Narrow NonConstantTimeCryptoComparison.ql to timing attack on signatures and MACs only

This commit is contained in:
Artem Smotrakov
2021-07-04 15:27:44 +02:00
committed by Fosstars
parent c359852608
commit 8b557765b3
4 changed files with 218 additions and 168 deletions

View File

@@ -3,16 +3,16 @@
<overview>
<p>
When comparing results of cryptographic operations, such as MAC or digital signature,
a constant-time algorithm should be used. In other words, the comparison time should not depend on
the content of the input. Otherwise, attackers may be able to implement a timing attack
if they can control input. A successful timing attack may result in leaking secrets or authentication bypass.
A constant-time algorithm should be used for checking a MAC or a digital signature.
In other words, the comparison time should not depend on the content of the input.
Otherwise, attackers may be able to implement a timing attack if they control inputs.
A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass.
</p>
</overview>
<recommendation>
<p>
Use <code>MessageDigest.isEqual()</code> method to compare results of cryptographic operations.
Use <code>MessageDigest.isEqual()</code> method to check MACs and signatures.
If this method is used, then the calculation time depends only on the length of input byte arrays,
and does not depend on the contents of the arrays.
</p>

View File

@@ -1,12 +1,12 @@
/**
* @name Using a non-constant-time algorithm for comparing results of a cryptographic operation
* @description When comparing results of a cryptographic operation, a constant-time algorithm should be used.
* Otherwise, attackers may be able to implement a timing attack if they can control input.
* A successful attack may result in leaking secrets or authentication bypass.
* @name Using a non-constant-time algorithm for comparing MAC or signature
* @description When checking MAC or signature, a constant-time algorithm should be used.
* Otherwise, attackers may be able to implement a timing attack if they control inputs.
* A successful attack may uncover a valid MAC or signature that in turn can result in authentication bypass.
* @kind path-problem
* @problem.severity warning
* @precision high
* @id java/non-constant-time-crypto-comparison
* @id java/non-constant-time-in-signature-check
* @tags security
* external/cwe/cwe-208
*/
@@ -25,6 +25,9 @@ abstract private class ProduceCryptoCall extends MethodAccess {
/** Return the result of cryptographic operation. */
Expr output() { result = output }
/** Return a type of the result of cryptographic operation such as MAC, signature or ciphertext. */
abstract string getResultType();
}
/** A method call that produces a MAC. */
@@ -37,6 +40,8 @@ private class ProduceMacCall extends ProduceCryptoCall {
getMethod().hasStringSignature("doFinal(byte[], int)") and getArgument(0) = output
)
}
override string getResultType() { result = "MAC" }
}
/** A method call that produces a signature. */
@@ -49,6 +54,8 @@ private class ProduceSignatureCall extends ProduceCryptoCall {
getMethod().hasStringSignature("sign(byte[], int, int)") and getArgument(0) = output
)
}
override string getResultType() { result = "signature" }
}
/**
@@ -98,6 +105,8 @@ private class ProduceCiphertextCall extends ProduceCryptoCall {
config.hasFlowTo(DataFlow3::exprNode(this.getQualifier()))
)
}
override string getResultType() { result = "ciphertext" }
}
/** Holds if `fromNode` to `toNode` is a dataflow step that updates a cryptographic operation. */
@@ -173,13 +182,9 @@ private class UserInputInCryptoOperationConfig extends TaintTracking2::Configura
/** A source that produces result of cryptographic operation. */
private class CryptoOperationSource extends DataFlow::Node {
Expr cryptoOperation;
ProduceCryptoCall call;
CryptoOperationSource() {
exists(ProduceCryptoCall call | call.output() = this.asExpr() |
cryptoOperation = call.getQualifier()
)
}
CryptoOperationSource() { call.output() = this.asExpr() }
/** Holds if remote user input was used in the cryptographic operation. */
predicate includesUserInput() {
@@ -188,9 +193,11 @@ private class CryptoOperationSource extends DataFlow::Node {
|
config.hasFlowPath(source, sink)
|
sink.getNode().asExpr() = cryptoOperation
sink.getNode().asExpr() = call.getQualifier()
)
}
ProduceCryptoCall getCall() { result = call }
}
/** Methods that use a non-constant-time algorithm for comparing inputs. */
@@ -329,8 +336,8 @@ from DataFlow::PathNode source, DataFlow::PathNode sink, NonConstantTimeCryptoCo
where
conf.hasFlowPath(source, sink) and
(
source.getNode().(CryptoOperationSource).includesUserInput() or
source.getNode().(CryptoOperationSource).includesUserInput() and
sink.getNode().(NonConstantTimeComparisonSink).includesUserInput()
)
select sink.getNode(), source, sink,
"Using a non-constant-time algorithm for comparing results of a cryptographic operation."
select sink.getNode(), source, sink, "Using a non-constant-time method for cheching a $@.", source,
source.getNode().(CryptoOperationSource).getCall().getResultType()

View File

@@ -1,50 +1,50 @@
edges
| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac |
| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) |
| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac |
| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature |
| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature |
| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:89:45:89:47 | tag |
| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:103:40:103:42 | tag |
| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) |
| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:129:49:129:51 | tag |
| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:147:40:147:42 | tag |
| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag |
| NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac |
| NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) |
| NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac |
| NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature |
| NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature |
| NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag |
| NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag |
| NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) |
| NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag |
| NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag |
| NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag |
nodes
| NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | semmle.label | actualMac |
| NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | semmle.label | castToObjectArray(...) |
| NonConstantTimeCryptoComparison.java:30:84:30:92 | actualMac : byte[] | semmle.label | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | semmle.label | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | semmle.label | actualMac |
| NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | semmle.label | sign(...) : byte[] |
| NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | semmle.label | signature |
| NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | semmle.label | signature : byte[] |
| NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | semmle.label | signature |
| NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | semmle.label | tag : byte[] |
| NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:118:40:118:42 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | semmle.label | array(...) |
| NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:146:22:146:46 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:147:40:147:42 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | semmle.label | computedTag |
| NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | semmle.label | actualMac |
| NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | semmle.label | castToObjectArray(...) |
| NonConstantTimeCryptoComparison.java:35:88:35:96 | actualMac : byte[] | semmle.label | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | semmle.label | actualMac : byte[] |
| NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | semmle.label | actualMac |
| NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | semmle.label | sign(...) : byte[] |
| NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | semmle.label | signature |
| NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | semmle.label | signature : byte[] |
| NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | semmle.label | signature |
| NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | semmle.label | tag : byte[] |
| NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:148:44:148:46 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | semmle.label | array(...) |
| NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | semmle.label | tag : ByteBuffer |
| NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | semmle.label | tag |
| NonConstantTimeCryptoComparison.java:220:34:220:50 | doFinal(...) : byte[] | semmle.label | doFinal(...) : byte[] |
| NonConstantTimeCryptoComparison.java:223:26:223:36 | computedTag | semmle.label | computedTag |
#select
| NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | NonConstantTimeCryptoComparison.java:20:28:20:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:21:43:21:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:29:28:29:40 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:30:66:30:93 | castToObjectArray(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | NonConstantTimeCryptoComparison.java:37:21:37:29 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:39:43:39:51 | actualMac | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | NonConstantTimeCryptoComparison.java:57:28:57:40 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:58:40:58:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | NonConstantTimeCryptoComparison.java:68:21:68:29 | signature : byte[] | NonConstantTimeCryptoComparison.java:69:40:69:48 | signature | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | NonConstantTimeCryptoComparison.java:87:22:87:41 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:89:45:89:47 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | NonConstantTimeCryptoComparison.java:102:24:102:26 | tag : byte[] | NonConstantTimeCryptoComparison.java:103:40:103:42 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | NonConstantTimeCryptoComparison.java:116:52:116:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:118:40:118:50 | array(...) | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | NonConstantTimeCryptoComparison.java:128:52:128:54 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:129:49:129:51 | tag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | NonConstantTimeCryptoComparison.java:177:34:177:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:180:26:180:36 | computedTag | Using a non-constant-time algorithm for comparing results of a cryptographic operation. |
| NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:23:47:23:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:21:32:21:48 | doFinal(...) : byte[] | MAC |
| NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:35:70:35:97 | castToObjectArray(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:33:32:33:44 | doFinal(...) : byte[] | MAC |
| NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | NonConstantTimeCryptoComparison.java:48:47:48:55 | actualMac | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:46:25:46:33 | actualMac : byte[] | MAC |
| NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | NonConstantTimeCryptoComparison.java:73:44:73:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:71:32:71:44 | sign(...) : byte[] | signature |
| NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | NonConstantTimeCryptoComparison.java:87:44:87:52 | signature | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:85:25:85:33 | signature : byte[] | signature |
| NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:113:49:113:51 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:111:26:111:45 | doFinal(...) : byte[] | ciphertext |
| NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | NonConstantTimeCryptoComparison.java:130:44:130:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:128:28:128:30 | tag : byte[] | ciphertext |
| NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:148:44:148:54 | array(...) | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:146:56:146:58 | tag : ByteBuffer | ciphertext |
| NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | NonConstantTimeCryptoComparison.java:162:53:162:55 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:160:56:160:58 | tag : ByteBuffer | ciphertext |
| NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | NonConstantTimeCryptoComparison.java:187:44:187:46 | tag | Using a non-constant-time method for cheching a $@. | NonConstantTimeCryptoComparison.java:185:26:185:50 | doFinal(...) : byte[] | ciphertext |

View File

@@ -13,74 +13,98 @@ import javax.crypto.Mac;
public class NonConstantTimeCryptoComparison {
// BAD: compare MACs using a non-constant-time method
public boolean unsafeMacCheckWithArrayEquals(byte[] expectedMac, Socket socket) throws Exception {
public boolean unsafeMacCheckWithArrayEquals(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024];
socket.getInputStream().read(data);
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 unsafeMacCheckWithArraysDeepEquals(byte[] expectedMac, Socket socket) throws Exception {
public boolean unsafeMacCheckWithArraysDeepEquals(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = socket.getInputStream().readAllBytes();
mac.update(data);
byte[] actualMac = mac.doFinal();
byte[] expectedMac = is.readNBytes(32);
return Arrays.deepEquals(castToObjectArray(expectedMac), castToObjectArray(actualMac));
}
}
// BAD: compare MACs using a non-constant-time method
public boolean unsafeMacCheckWithDoFinalWithOutputArray(byte[] data, Socket socket) throws Exception {
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(byte[] expectedMac, Socket socket) throws Exception {
public boolean saferMacCheck(Socket socket) throws Exception {
try (InputStream is = socket.getInputStream()) {
Mac mac = Mac.getInstance("HmacSHA256");
byte[] data = new byte[1024];
socket.getInputStream().read(data);
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(byte[] expected, Socket socket, PrivateKey key) throws Exception {
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(byte[] expected, Socket socket, PrivateKey key) throws Exception {
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(byte[] expected, Socket socket, PrivateKey key) throws Exception {
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, byte[] plaintext, Key key) throws Exception {
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);
@@ -88,9 +112,11 @@ public class NonConstantTimeCryptoComparison {
byte[] expected = socket.getInputStream().readAllBytes();
return Objects.deepEquals(expected, tag);
}
}
// BAD: compare ciphertexts (custom MAC) using a non-constant-time method
public boolean unsafeCheckCiphertextWithOutputArray(byte[] expected, Socket socket, Key key) throws Exception {
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);
@@ -100,11 +126,15 @@ public class NonConstantTimeCryptoComparison {
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, byte[] plaintext, Key key) throws Exception {
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];
@@ -117,20 +147,26 @@ public class NonConstantTimeCryptoComparison {
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(byte[] expected, Socket socket, Key key) throws Exception {
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, byte[] plaintext, Key key) throws Exception {
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);
@@ -138,23 +174,30 @@ public class NonConstantTimeCryptoComparison {
byte[] expected = socket.getInputStream().readAllBytes();
return MessageDigest.isEqual(expected, tag);
}
}
// GOOD: compare ciphertexts using a constant-time method, but no user input
public boolean noUserInputWhenCheckingCiphertext(byte[] expected, byte[] plaintext, Key key) throws Exception {
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));
}
}
private static Object[] castToObjectArray(byte[] array) {
Object[] result = new Object[array.length];