Skip to content

Commit 218731c

Browse files
Added a query for static initialization vectors in encryption
- Added StaticInitializationVector.ql - Added StaticInitializationVector.qhelp - Added tests
1 parent 68b3c28 commit 218731c

File tree

7 files changed

+380
-0
lines changed

7 files changed

+380
-0
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
byte[] iv = new byte[16]; // all zeroes
2+
GCMParameterSpec params = new GCMParameterSpec(128, iv);
3+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
4+
cipher.init(Cipher.ENCRYPT_MODE, key, params);
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
byte[] iv = new byte[16];
2+
SecureRandom random = SecureRandom.getInstanceStrong();
3+
random.nextBytes(iv);
4+
GCMParameterSpec params = new GCMParameterSpec(128, iv);
5+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
6+
cipher.init(Cipher.ENCRYPT_MODE, key, params);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
A cipher needs an initialization vector (IV) when it is used in certain modes
7+
such as CBC or GCM. Under the same secret key, IVs should be unique and ideally unpredictable.
8+
Given a secret key, if the same IV is used for encryption, the same plaintexts result in the same ciphertexts.
9+
This lets an attacker learn if the same data pieces are transfered or stored,
10+
or this can help the attacker run a dictionary attack.
11+
</p>
12+
</overview>
13+
14+
<recommendation>
15+
<p>
16+
Use a random IV generated by <code>SecureRandom</code>.
17+
</p>
18+
</recommendation>
19+
20+
<example>
21+
<p>
22+
The following example initializes a cipher with a static IV which is unsafe:
23+
</p>
24+
<sample src="BadStaticInitializationVector.java" />
25+
26+
<p>
27+
The next example initializes a cipher with a random IV:
28+
</p>
29+
<sample src="GoodRandomInitializationVector.java" />
30+
</example>
31+
32+
<references>
33+
<li>
34+
Wikipedia:
35+
<a href="https://en.wikipedia.org/wiki/Initialization_vector">Initialization vector</a>.
36+
</li>
37+
<li>
38+
National Institute of Standards and Technology:
39+
<a href="https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-38a.pdf">Recommendation for Block Cipher Modes of Operation</a>.
40+
</li>
41+
<li>
42+
National Institute of Standards and Technology:
43+
<a href="https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.140-2.pdf">FIPS 140-2: Security Requirements for Cryptographic Modules</a>.
44+
</li>
45+
</references>
46+
</qhelp>
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/**
2+
* @name Using a static initialization vector for encryption
3+
* @description A cipher needs an initialization vector (IV) in some cases,
4+
* for example, when CBC or GCM modes are used. IVs are used to randomize the encryption,
5+
* therefore they should be unique and ideally unpredictable.
6+
* Otherwise, the same plaintexts result in same ciphertexts under a given secret key.
7+
* If a static IV is used for encryption, this lets an attacker learn
8+
* if the same data pieces are transfered or stored,
9+
* or this can help the attacker run a dictionary attack.
10+
* @kind path-problem
11+
* @problem.severity warning
12+
* @precision high
13+
* @id java/static-initialization-vector
14+
* @tags security
15+
* external/cwe/cwe-329
16+
* external/cwe/cwe-1204
17+
*/
18+
19+
import java
20+
import semmle.code.java.dataflow.TaintTracking
21+
import semmle.code.java.dataflow.TaintTracking2
22+
import DataFlow::PathGraph
23+
24+
/**
25+
* Holds if `array` is initialized only with constants, for example,
26+
* `new byte[8]` or `new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }`.
27+
*/
28+
private predicate initializedWithConstants(ArrayCreationExpr array) {
29+
not exists(array.getInit())
30+
or
31+
forex(Expr element | element = array.getInit().getAChildExpr() |
32+
element instanceof CompileTimeConstantExpr
33+
)
34+
}
35+
36+
/**
37+
* An expression that creates a byte array that is initialized with constants.
38+
*/
39+
private class StaticByteArrayCreation extends ArrayCreationExpr {
40+
StaticByteArrayCreation() {
41+
this.getType().(Array).getElementType().(PrimitiveType).getName() = "byte" and
42+
initializedWithConstants(this)
43+
}
44+
}
45+
46+
/** Defines a sub-set of expressions that update an array. */
47+
private class ArrayUpdate extends Expr {
48+
Expr array;
49+
50+
ArrayUpdate() {
51+
exists(Assignment assign, ArrayAccess arrayAccess | arrayAccess = assign.getDest() |
52+
assign = this and
53+
arrayAccess.getArray() = array and
54+
not assign.getSource() instanceof CompileTimeConstantExpr
55+
)
56+
or
57+
exists(StaticMethodAccess ma |
58+
ma.getMethod().hasQualifiedName("java.lang", "System", "arraycopy") and
59+
ma = this and
60+
ma.getArgument(2) = array
61+
)
62+
or
63+
exists(StaticMethodAccess ma |
64+
ma.getMethod().hasQualifiedName("java.util", "Arrays", "copyOf") and
65+
ma = this and
66+
ma = array
67+
)
68+
or
69+
exists(MethodAccess ma, Method m |
70+
m = ma.getMethod() and
71+
ma = this and
72+
ma.getArgument(0) = array
73+
|
74+
m.hasQualifiedName("java.io", "InputStream", "read") or
75+
m.hasQualifiedName("java.nio", "ByteBuffer", "get") or
76+
m.hasQualifiedName("java.security", "SecureRandom", "nextBytes") or
77+
m.hasQualifiedName("java.util", "Random", "nextBytes")
78+
)
79+
}
80+
81+
/** Returns the updated array. */
82+
Expr getArray() { result = array }
83+
}
84+
85+
/**
86+
* A config that tracks dataflow from creating an array to an operation that updates it.
87+
*/
88+
private class ArrayUpdateConfig extends TaintTracking2::Configuration {
89+
ArrayUpdateConfig() { this = "ArrayUpdateConfig" }
90+
91+
override predicate isSource(DataFlow::Node source) {
92+
source.asExpr() instanceof StaticByteArrayCreation
93+
}
94+
95+
override predicate isSink(DataFlow::Node sink) {
96+
exists(ArrayUpdate update | update.getArray() = sink.asExpr())
97+
}
98+
}
99+
100+
/**
101+
* A source that defines an array that doesn't get updated.
102+
*/
103+
private class StaticInitializationVectorSource extends DataFlow::Node {
104+
StaticInitializationVectorSource() {
105+
exists(StaticByteArrayCreation array | array = this.asExpr() |
106+
not exists(ArrayUpdate update, ArrayUpdateConfig config |
107+
config.hasFlow(DataFlow2::exprNode(array), DataFlow2::exprNode(update.getArray()))
108+
)
109+
)
110+
}
111+
}
112+
113+
/**
114+
* A config that tracks initialization of a cipher for encryption.
115+
*/
116+
private class EncryptionModeConfig extends TaintTracking2::Configuration {
117+
EncryptionModeConfig() { this = "EncryptionModeConfig" }
118+
119+
override predicate isSource(DataFlow::Node source) {
120+
source.asExpr().(VarAccess).getVariable().hasName("ENCRYPT_MODE")
121+
}
122+
123+
override predicate isSink(DataFlow::Node sink) {
124+
exists(MethodAccess ma, Method m | m = ma.getMethod() |
125+
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
126+
ma.getArgument(0) = sink.asExpr()
127+
)
128+
}
129+
}
130+
131+
/**
132+
* A sink that initializes a cipher for encryption with unsafe parameters.
133+
*/
134+
private class EncryptionInitializationSink extends DataFlow::Node {
135+
EncryptionInitializationSink() {
136+
exists(MethodAccess ma, Method m, EncryptionModeConfig config | m = ma.getMethod() |
137+
m.hasQualifiedName("javax.crypto", "Cipher", "init") and
138+
m.getParameterType(2)
139+
.(RefType)
140+
.hasQualifiedName("java.security.spec", "AlgorithmParameterSpec") and
141+
ma.getArgument(2) = this.asExpr() and
142+
config.hasFlowToExpr(ma.getArgument(0))
143+
)
144+
}
145+
}
146+
147+
/**
148+
* Holds if `fromNode` to `toNode` is a dataflow step
149+
* that creates cipher's parameters with initialization vector.
150+
*/
151+
private predicate createInitializationVectorSpecStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
152+
exists(ConstructorCall cc, RefType type |
153+
cc = toNode.asExpr() and type = cc.getConstructedType()
154+
|
155+
type.hasQualifiedName("javax.crypto.spec", "IvParameterSpec") and
156+
cc.getArgument(0) = fromNode.asExpr()
157+
or
158+
type.hasQualifiedName("javax.crypto.spec", ["GCMParameterSpec", "RC2ParameterSpec"]) and
159+
cc.getArgument(1) = fromNode.asExpr()
160+
or
161+
type.hasQualifiedName("javax.crypto.spec", "RC5ParameterSpec") and
162+
cc.getArgument(3) = fromNode.asExpr()
163+
)
164+
}
165+
166+
/**
167+
* A config that tracks dataflow to initializing a cipher with a static initialization vector.
168+
*/
169+
private class StaticInitializationVectorConfig extends TaintTracking::Configuration {
170+
StaticInitializationVectorConfig() { this = "StaticInitializationVectorConfig" }
171+
172+
override predicate isSource(DataFlow::Node source) {
173+
source instanceof StaticInitializationVectorSource
174+
}
175+
176+
override predicate isSink(DataFlow::Node sink) { sink instanceof EncryptionInitializationSink }
177+
178+
override predicate isAdditionalTaintStep(DataFlow::Node fromNode, DataFlow::Node toNode) {
179+
createInitializationVectorSpecStep(fromNode, toNode)
180+
}
181+
182+
override predicate isSanitizer(DataFlow::Node node) {
183+
exists(ArrayUpdate update | update.getArray() = node.asExpr())
184+
}
185+
}
186+
187+
from DataFlow::PathNode source, DataFlow::PathNode sink, StaticInitializationVectorConfig conf
188+
where conf.hasFlowPath(source, sink)
189+
select sink.getNode(), source, sink, "A $@ should not be used for encryption.", source.getNode(),
190+
"static initialization vector"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
edges
2+
| StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | StaticInitializationVector.java:19:51:19:56 | ivSpec |
3+
| StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | StaticInitializationVector.java:32:51:32:56 | ivSpec |
4+
| StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | StaticInitializationVector.java:48:51:48:56 | ivSpec |
5+
nodes
6+
| StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
7+
| StaticInitializationVector.java:19:51:19:56 | ivSpec | semmle.label | ivSpec |
8+
| StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
9+
| StaticInitializationVector.java:32:51:32:56 | ivSpec | semmle.label | ivSpec |
10+
| StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | semmle.label | new byte[] : byte[] |
11+
| StaticInitializationVector.java:48:51:48:56 | ivSpec | semmle.label | ivSpec |
12+
#select
13+
| StaticInitializationVector.java:19:51:19:56 | ivSpec | StaticInitializationVector.java:13:21:13:81 | new byte[] : byte[] | StaticInitializationVector.java:19:51:19:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:13:21:13:81 | new byte[] | static initialization vector |
14+
| StaticInitializationVector.java:32:51:32:56 | ivSpec | StaticInitializationVector.java:26:21:26:32 | new byte[] : byte[] | StaticInitializationVector.java:32:51:32:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:26:21:26:32 | new byte[] | static initialization vector |
15+
| StaticInitializationVector.java:48:51:48:56 | ivSpec | StaticInitializationVector.java:39:21:39:32 | new byte[] : byte[] | StaticInitializationVector.java:48:51:48:56 | ivSpec | A $@ should not be used for encryption. | StaticInitializationVector.java:39:21:39:32 | new byte[] | static initialization vector |
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import javax.crypto.Cipher;
2+
import javax.crypto.spec.GCMParameterSpec;
3+
import javax.crypto.spec.IvParameterSpec;
4+
import javax.crypto.spec.SecretKeySpec;
5+
6+
import java.security.SecureRandom;
7+
import java.util.Arrays;
8+
9+
public class StaticInitializationVector {
10+
11+
// BAD: AES-GCM with static IV from a byte array
12+
public byte[] encryptWithStaticIvByteArrayWithInitializer(byte[] key, byte[] plaintext) throws Exception {
13+
byte[] iv = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5 };
14+
15+
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
16+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
17+
18+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
19+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
20+
cipher.update(plaintext);
21+
return cipher.doFinal();
22+
}
23+
24+
// BAD: AES-GCM with static IV from zero-initialized byte array
25+
public byte[] encryptWithZeroStaticIvByteArray(byte[] key, byte[] plaintext) throws Exception {
26+
byte[] iv = new byte[16];
27+
28+
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
29+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
30+
31+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
32+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
33+
cipher.update(plaintext);
34+
return cipher.doFinal();
35+
}
36+
37+
// BAD: AES-CBC with static IV from zero-initialized byte array
38+
public byte[] encryptWithStaticIvByteArray(byte[] key, byte[] plaintext) throws Exception {
39+
byte[] iv = new byte[16];
40+
for (byte i = 0; i < iv.length; i++) {
41+
iv[i] = 1;
42+
}
43+
44+
IvParameterSpec ivSpec = new IvParameterSpec(iv);
45+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
46+
47+
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
48+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
49+
cipher.update(plaintext);
50+
return cipher.doFinal();
51+
}
52+
53+
// GOOD: AES-GCM with a random IV
54+
public byte[] encryptWithRandomIv(byte[] key, byte[] plaintext) throws Exception {
55+
byte[] iv = new byte[16];
56+
57+
SecureRandom random = SecureRandom.getInstanceStrong();
58+
random.nextBytes(iv);
59+
60+
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
61+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
62+
63+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
64+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
65+
cipher.update(plaintext);
66+
return cipher.doFinal();
67+
}
68+
69+
// GOOD: AES-GCM with a random IV
70+
public byte[] encryptWithRandomIvByteByByte(byte[] key, byte[] plaintext) throws Exception {
71+
SecureRandom random = SecureRandom.getInstanceStrong();
72+
byte[] iv = new byte[16];
73+
for (int i = 0; i < iv.length; i++) {
74+
iv[i] = (byte) random.nextInt();
75+
}
76+
77+
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
78+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
79+
80+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
81+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
82+
cipher.update(plaintext);
83+
return cipher.doFinal();
84+
}
85+
86+
// GOOD: AES-GCM with a random IV
87+
public byte[] encryptWithRandomIvWithSystemArrayCopy(byte[] key, byte[] plaintext) throws Exception {
88+
byte[] randomBytes = new byte[16];
89+
SecureRandom.getInstanceStrong().nextBytes(randomBytes);
90+
91+
byte[] iv = new byte[16];
92+
System.arraycopy(randomBytes, 0, iv, 0, 16);
93+
94+
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
95+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
96+
97+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
98+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
99+
cipher.update(plaintext);
100+
return cipher.doFinal();
101+
}
102+
103+
// GOOD: AES-GCM with a random IV
104+
public byte[] encryptWithRandomIvWithArraysCopy(byte[] key, byte[] plaintext) throws Exception {
105+
byte[] randomBytes = new byte[16];
106+
SecureRandom.getInstanceStrong().nextBytes(randomBytes);
107+
108+
byte[] iv = Arrays.copyOf(randomBytes, 16);
109+
110+
GCMParameterSpec ivSpec = new GCMParameterSpec(128, iv);
111+
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
112+
113+
Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5PADDING");
114+
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
115+
cipher.update(plaintext);
116+
return cipher.doFinal();
117+
}
118+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
experimental/Security/CWE/CWE-1204/StaticInitializationVector.ql

0 commit comments

Comments
 (0)