Merge pull request #6006 from artem-smotrakov/timing-attacks

Java: Timing attacks while comparing results of cryptographic operations
This commit is contained in:
Chris Smowton
2021-08-09 15:30:47 +01:00
committed by GitHub
15 changed files with 814 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
edges
| Test.java:14:28:14:44 | doFinal(...) : byte[] | Test.java:15:43:15:51 | actualMac |
| Test.java:30:28:30:40 | sign(...) : byte[] | Test.java:31:40:31:48 | signature |
| Test.java:47:22:47:46 | doFinal(...) : byte[] | Test.java:48:40:48:42 | tag |
nodes
| Test.java:14:28:14:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| Test.java:15:43:15:51 | actualMac | semmle.label | actualMac |
| Test.java:30:28:30:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] |
| Test.java:31:40:31:48 | signature | semmle.label | signature |
| Test.java:47:22:47:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| Test.java:48:40:48:42 | tag | semmle.label | tag |
#select
| Test.java:15:43:15:51 | actualMac | Test.java:14:28:14:44 | doFinal(...) : byte[] | Test.java:15:43:15:51 | actualMac | Possible timing attack against $@ validation. | Test.java:14:28:14:44 | doFinal(...) : byte[] | MAC |
| Test.java:31:40:31:48 | signature | Test.java:30:28:30:40 | sign(...) : byte[] | Test.java:31:40:31:48 | signature | Possible timing attack against $@ validation. | Test.java:30:28:30:40 | sign(...) : byte[] | signature |
| Test.java:48:40:48:42 | tag | Test.java:47:22:47:46 | doFinal(...) : byte[] | Test.java:48:40:48:42 | tag | Possible timing attack against $@ validation. | Test.java:47:22:47:46 | doFinal(...) : byte[] | ciphertext |

View File

@@ -0,0 +1,59 @@
import java.security.Key;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.Mac;
public class Test {
// BAD: compare MACs using a not-constant time method
public boolean unsafeMacCheck(byte[] expectedMac, byte[] data) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] actualMac = mac.doFinal(data);
return Arrays.equals(expectedMac, actualMac);
}
// GOOD: compare MACs using a constant time method
public boolean saferMacCheck(byte[] expectedMac, byte[] data) throws Exception {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] actualMac = mac.doFinal(data);
return MessageDigest.isEqual(expectedMac, actualMac);
}
// BAD: compare signatures using a not-constant time method
public boolean unsafeCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception {
Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key);
engine.update(data);
byte[] signature = engine.sign();
return Arrays.equals(expected, signature);
}
// GOOD: compare signatures using a constant time method
public boolean saferCheckSignatures(byte[] expected, byte[] data, PrivateKey key) throws Exception {
Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key);
engine.update(data);
byte[] signature = engine.sign();
return MessageDigest.isEqual(expected, signature);
}
// BAD: compare ciphertexts using a not-constant time method
public boolean unsafeCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] tag = cipher.doFinal(plaintext);
return Arrays.equals(expected, tag);
}
// GOOD: compare ciphertexts using a constant time method
public boolean saferCheckCustomMac(byte[] expected, byte[] plaintext, Key key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] tag = cipher.doFinal(plaintext);
return MessageDigest.isEqual(expected, tag);
}
}

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-208/PossibleTimingAttackAgainstSignature.ql

View File

@@ -0,0 +1,44 @@
edges
| Test.java:21:32:21:48 | doFinal(...) : byte[] | Test.java:23:47:23:55 | actualMac |
| Test.java:34:25:34:33 | actualMac : byte[] | Test.java:36:47:36:55 | actualMac |
| Test.java:59:32:59:44 | sign(...) : byte[] | Test.java:61:44:61:52 | signature |
| Test.java:73:25:73:33 | signature : byte[] | Test.java:75:44:75:52 | signature |
| Test.java:99:26:99:45 | doFinal(...) : byte[] | Test.java:101:49:101:51 | tag |
| Test.java:116:28:116:30 | tag : byte[] | Test.java:118:44:118:46 | tag |
| Test.java:134:56:134:58 | tag : ByteBuffer | Test.java:136:44:136:46 | tag : ByteBuffer |
| Test.java:136:44:136:46 | tag : ByteBuffer | Test.java:136:44:136:54 | array(...) |
| Test.java:148:56:148:58 | tag : ByteBuffer | Test.java:150:53:150:55 | tag |
| Test.java:174:26:174:50 | doFinal(...) : byte[] | Test.java:176:44:176:46 | tag |
| Test.java:201:34:201:50 | doFinal(...) : byte[] | Test.java:204:26:204:36 | computedTag |
nodes
| Test.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| Test.java:23:47:23:55 | actualMac | semmle.label | actualMac |
| Test.java:34:25:34:33 | actualMac : byte[] | semmle.label | actualMac : byte[] |
| Test.java:36:47:36:55 | actualMac | semmle.label | actualMac |
| Test.java:59:32:59:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] |
| Test.java:61:44:61:52 | signature | semmle.label | signature |
| Test.java:73:25:73:33 | signature : byte[] | semmle.label | signature : byte[] |
| Test.java:75:44:75:52 | signature | semmle.label | signature |
| Test.java:99:26:99:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| Test.java:101:49:101:51 | tag | semmle.label | tag |
| Test.java:116:28:116:30 | tag : byte[] | semmle.label | tag : byte[] |
| Test.java:118:44:118:46 | tag | semmle.label | tag |
| Test.java:134:56:134:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| Test.java:136:44:136:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| Test.java:136:44:136:54 | array(...) | semmle.label | array(...) |
| Test.java:148:56:148:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| Test.java:150:53:150:55 | tag | semmle.label | tag |
| Test.java:174:26:174:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| Test.java:176:44:176:46 | tag | semmle.label | tag |
| Test.java:201:34:201:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| Test.java:204:26:204:36 | computedTag | semmle.label | computedTag |
#select
| Test.java:23:47:23:55 | actualMac | Test.java:21:32:21:48 | doFinal(...) : byte[] | Test.java:23:47:23:55 | actualMac | Timing attack against $@ validation. | Test.java:21:32:21:48 | doFinal(...) : byte[] | MAC |
| Test.java:36:47:36:55 | actualMac | Test.java:34:25:34:33 | actualMac : byte[] | Test.java:36:47:36:55 | actualMac | Timing attack against $@ validation. | Test.java:34:25:34:33 | actualMac : byte[] | MAC |
| Test.java:61:44:61:52 | signature | Test.java:59:32:59:44 | sign(...) : byte[] | Test.java:61:44:61:52 | signature | Timing attack against $@ validation. | Test.java:59:32:59:44 | sign(...) : byte[] | signature |
| Test.java:75:44:75:52 | signature | Test.java:73:25:73:33 | signature : byte[] | Test.java:75:44:75:52 | signature | Timing attack against $@ validation. | Test.java:73:25:73:33 | signature : byte[] | signature |
| Test.java:101:49:101:51 | tag | Test.java:99:26:99:45 | doFinal(...) : byte[] | Test.java:101:49:101:51 | tag | Timing attack against $@ validation. | Test.java:99:26:99:45 | doFinal(...) : byte[] | ciphertext |
| Test.java:118:44:118:46 | tag | Test.java:116:28:116:30 | tag : byte[] | Test.java:118:44:118:46 | tag | Timing attack against $@ validation. | Test.java:116:28:116:30 | tag : byte[] | ciphertext |
| Test.java:136:44:136:54 | array(...) | Test.java:134:56:134:58 | tag : ByteBuffer | Test.java:136:44:136:54 | array(...) | Timing attack against $@ validation. | Test.java:134:56:134:58 | tag : ByteBuffer | ciphertext |
| Test.java:150:53:150:55 | tag | Test.java:148:56:148:58 | tag : ByteBuffer | Test.java:150:53:150:55 | tag | Timing attack against $@ validation. | Test.java:148:56:148:58 | tag : ByteBuffer | ciphertext |
| Test.java:176:44:176:46 | tag | Test.java:174:26:174:50 | doFinal(...) : byte[] | Test.java:176:44:176:46 | tag | Timing attack against $@ validation. | Test.java:174:26:174:50 | doFinal(...) : byte[] | ciphertext |

View File

@@ -0,0 +1,236 @@
import java.io.InputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.security.Key;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.Signature;
import java.util.Arrays;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.Mac;
public class Test {
// BAD: compare MACs using a non-constant-time method
public boolean unsafeMacCheckWithArrayEquals(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024];
is.read(data);
byte[] actualMac = mac.doFinal(data);
byte[] expectedMac = is.readNBytes(32);
return Arrays.equals(expectedMac, actualMac);
}
}
// BAD: compare MACs using a non-constant-time method
public boolean unsafeMacCheckWithDoFinalWithOutputArray(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] data = is.readNBytes(100);
Mac mac = Mac.getInstance("HmacSHA256");
byte[] actualMac = new byte[256];
mac.update(data);
mac.doFinal(actualMac, 0);
byte[] expectedMac = socket.getInputStream().readNBytes(256);
return Arrays.equals(expectedMac, actualMac);
}
}
// GOOD: compare MACs using a constant-time method
public boolean saferMacCheck(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024];
is.read(data);
byte[] actualMac = mac.doFinal(data);
byte[] expectedMac = is.readNBytes(32);
return MessageDigest.isEqual(expectedMac, actualMac);
}
}
// BAD: compare signatures using a non-constant-time method
public boolean unsafeCheckSignatures(Socket socket, PrivateKey key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key);
byte[] data = socket.getInputStream().readAllBytes();
engine.update(data);
byte[] signature = engine.sign();
byte[] expected = is.readNBytes(256);
return Arrays.equals(expected, signature);
}
}
// BAD: compare signatures using a non-constant-time method
public boolean unsafeCheckSignaturesWithOutputArray(Socket socket, PrivateKey key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key);
byte[] data = socket.getInputStream().readAllBytes();
engine.update(data);
byte[] signature = new byte[1024];
engine.sign(signature, 0, 1024);
byte[] expected = is.readNBytes(256);
return Arrays.equals(expected, signature);
}
}
// GOOD: compare signatures using a constant-time method
public boolean saferCheckSignatures(Socket socket, PrivateKey key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Signature engine = Signature.getInstance("SHA256withRSA");
engine.initSign(key);
byte[] data = socket.getInputStream().readAllBytes();
engine.update(data);
byte[] signature = engine.sign();
byte[] expected = is.readNBytes(256);
return MessageDigest.isEqual(expected, signature);
}
}
// BAD: compare ciphertexts (custom MAC) using a non-constant-time method
public boolean unsafeCheckCiphertext(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] plaintext = is.readNBytes(100);
byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] tag = cipher.doFinal(hash);
byte[] expected = socket.getInputStream().readAllBytes();
return Objects.deepEquals(expected, tag);
}
}
// BAD: compare ciphertexts (custom MAC) using a non-constant-time method
public boolean unsafeCheckCiphertextWithOutputArray(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] plaintext = socket.getInputStream().readAllBytes();
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(plaintext);
byte[] hash = md.digest();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
cipher.update(hash);
byte[] tag = new byte[1024];
cipher.doFinal(tag, 0);
byte[] expected = is.readNBytes(32);
return Arrays.equals(expected, tag);
}
}
// BAD: compare ciphertexts (custom MAC) using a non-constant-time method
public boolean unsafeCheckCiphertextWithByteBuffer(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] plaintext = is.readNBytes(300);
MessageDigest md = MessageDigest.getInstance("SHA-512");
md.update(plaintext);
byte[] hash = new byte[1024];
md.digest(hash, 0, hash.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
cipher.update(hash);
ByteBuffer tag = ByteBuffer.wrap(new byte[1024]);
cipher.doFinal(ByteBuffer.wrap(plaintext), tag);
byte[] expected = socket.getInputStream().readNBytes(1024);
return Arrays.equals(expected, tag.array());
}
}
// BAD: compare ciphertexts (custom MAC) using a non-constant-time method
public boolean unsafeCheckCiphertextWithByteBufferEquals(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] plaintext = socket.getInputStream().readAllBytes();
cipher.update(plaintext);
ByteBuffer tag = ByteBuffer.wrap(new byte[1024]);
cipher.doFinal(ByteBuffer.wrap(plaintext), tag);
byte[] expected = is.readNBytes(32);
return ByteBuffer.wrap(expected).equals(tag);
}
}
// GOOD: compare ciphertexts (custom MAC) using a constant-time method
public boolean saferCheckCiphertext(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] plaintext = is.readNBytes(200);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] hash = MessageDigest.getInstance("SHA-256").digest(plaintext);
byte[] tag = cipher.doFinal(hash);
byte[] expected = socket.getInputStream().readAllBytes();
return MessageDigest.isEqual(expected, tag);
}
}
// GOOD: compare ciphertexts using a constant-time method, but no user input
// but NonConstantTimeCheckOnSignature.ql still detects it
public boolean noUserInputWhenCheckingCiphertext(Socket socket, Key key) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] plaintext = is.readNBytes(100);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] tag = cipher.doFinal(plaintext);
byte[] expected = is.readNBytes(32);
return Arrays.equals(expected, tag);
}
}
// GOOD: compare MAC with constant using a constant-time method
public boolean compareMacWithConstant(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024];
socket.getInputStream().read(data);
byte[] actualMac = mac.doFinal(data);
return "constant".equals(new String(actualMac));
}
}
// BAD: compare MAC using a non-constant-time loop
public boolean unsafeMacCheckWithLoop(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] data = new byte[256];
byte[] tag = new byte[32];
is.read(data);
is.read(tag);
Mac mac = Mac.getInstance("Hmac256");
byte[] computedTag = mac.doFinal(data);
for (int i = 0; i < computedTag.length; i++) {
byte a = computedTag[i];
byte b = tag[i];
if (a != b) {
return false;
}
}
return true;
}
}
// GOOD: compare MAC using a constant-time loop
public boolean safeMacCheckWithLoop(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
byte[] data = new byte[256];
byte[] tag = new byte[32];
is.read(data);
is.read(tag);
Mac mac = Mac.getInstance("Hmac256");
byte[] computedTag = mac.doFinal(data);
int result = 0;
for (int i = 0; i < computedTag.length; i++) {
result |= computedTag[i] ^ tag[i];
}
return result == 0;
}
}
}

View File

@@ -0,0 +1 @@
experimental/Security/CWE/CWE-208/TimingAttackAgainstSignature.ql