25
25
import com .google .crypto .tink .annotations .Alpha ;
26
26
import com .google .crypto .tink .subtle .Bytes ;
27
27
import com .google .crypto .tink .subtle .EngineFactory ;
28
+ import com .google .crypto .tink .subtle .Hex ;
28
29
import com .google .crypto .tink .subtle .Random ;
29
30
import com .google .crypto .tink .subtle .SubtleUtil ;
30
31
import com .google .crypto .tink .subtle .Validators ;
49
50
*/
50
51
@ Alpha
51
52
public final class AesGcmSiv implements Aead {
52
- private static final ThreadLocal <Cipher > localCipher =
53
+
54
+ // Test vector from https://www.rfc-editor.org/rfc/rfc8452.html#appendix-C.1
55
+ private static final byte [] TEST_PLAINTEXT = Hex .decode ("7a806c" );
56
+ private static final byte [] TEST_AAD = Hex .decode ("46bb91c3c5" );
57
+ private static final byte [] TEST_KEY = Hex .decode ("36864200e0eaf5284d884a0e77d31646" );
58
+ private static final byte [] TEST_NOUNCE = Hex .decode ("bae8e37fc83441b16034566b" );
59
+ private static final byte [] TEST_RESULT = Hex .decode ("af60eb711bd85bc1e4d3e0a462e074eea428a8" );
60
+
61
+ // On Android API version 29 and older, the security provider returns an AES GCM cipher instead
62
+ // an AES GCM SIV cipher. This function tests if we have a correct cipher.
63
+ private static boolean isAesGcmSivCipher (Cipher cipher ) {
64
+ try {
65
+ // Use test vector to validate that cipher implements AES GCM SIV.
66
+ AlgorithmParameterSpec params = getParams (TEST_NOUNCE );
67
+ cipher .init (Cipher .DECRYPT_MODE , new SecretKeySpec (TEST_KEY , "AES" ), params );
68
+ cipher .updateAAD (TEST_AAD );
69
+ byte [] output = cipher .doFinal (TEST_RESULT , 0 , TEST_RESULT .length );
70
+ return Bytes .equal (output , TEST_PLAINTEXT );
71
+ } catch (GeneralSecurityException ex ) {
72
+ return false ;
73
+ }
74
+ }
75
+
76
+ // localAesGcmSivCipher.get() may be null if the cipher returned by EngineFactory is not a valid
77
+ // AES GCM SIV cipher.
78
+ private static final ThreadLocal <Cipher > localAesGcmSivCipher =
53
79
new ThreadLocal <Cipher >() {
54
80
@ Override
55
81
protected Cipher initialValue () {
56
82
try {
57
- return EngineFactory .CIPHER .getInstance ("AES/GCM-SIV/NoPadding" );
83
+ Cipher cipher = EngineFactory .CIPHER .getInstance ("AES/GCM-SIV/NoPadding" );
84
+ if (!isAesGcmSivCipher (cipher )) {
85
+ return null ;
86
+ }
87
+ return cipher ;
58
88
} catch (GeneralSecurityException ex ) {
59
89
throw new IllegalStateException (ex );
60
90
}
@@ -86,8 +116,17 @@ public AesGcmSiv(final byte[] key) throws GeneralSecurityException {
86
116
this (key , new byte [0 ]);
87
117
}
88
118
119
+ private Cipher getAesGcmSivCipher () throws GeneralSecurityException {
120
+ Cipher cipher = localAesGcmSivCipher .get ();
121
+ if (cipher == null ) {
122
+ throw new GeneralSecurityException ("AES GCM SIV cipher is not available or is invalid." );
123
+ }
124
+ return cipher ;
125
+ }
126
+
89
127
private byte [] rawEncrypt (final byte [] plaintext , final byte [] associatedData )
90
128
throws GeneralSecurityException {
129
+ Cipher cipher = getAesGcmSivCipher ();
91
130
// Check that ciphertext is not longer than the max. size of a Java array.
92
131
if (plaintext .length > Integer .MAX_VALUE - IV_SIZE_IN_BYTES - TAG_SIZE_IN_BYTES ) {
93
132
throw new GeneralSecurityException ("plaintext too long" );
@@ -97,12 +136,11 @@ private byte[] rawEncrypt(final byte[] plaintext, final byte[] associatedData)
97
136
System .arraycopy (iv , 0 , ciphertext , 0 , IV_SIZE_IN_BYTES );
98
137
99
138
AlgorithmParameterSpec params = getParams (iv );
100
- localCipher . get () .init (Cipher .ENCRYPT_MODE , keySpec , params );
139
+ cipher .init (Cipher .ENCRYPT_MODE , keySpec , params );
101
140
if (associatedData != null && associatedData .length != 0 ) {
102
- localCipher . get () .updateAAD (associatedData );
141
+ cipher .updateAAD (associatedData );
103
142
}
104
- int written =
105
- localCipher .get ().doFinal (plaintext , 0 , plaintext .length , ciphertext , IV_SIZE_IN_BYTES );
143
+ int written = cipher .doFinal (plaintext , 0 , plaintext .length , ciphertext , IV_SIZE_IN_BYTES );
106
144
// For security reasons, AES-GCM encryption must always use tag of TAG_SIZE_IN_BYTES bytes. If
107
145
// so, written must be equal to plaintext.length + TAG_SIZE_IN_BYTES.
108
146
@@ -134,18 +172,17 @@ public byte[] encrypt(final byte[] plaintext, final byte[] associatedData)
134
172
135
173
private byte [] rawDecrypt (final byte [] ciphertext , final byte [] associatedData )
136
174
throws GeneralSecurityException {
175
+ Cipher cipher = getAesGcmSivCipher ();
137
176
if (ciphertext .length < IV_SIZE_IN_BYTES + TAG_SIZE_IN_BYTES ) {
138
177
throw new GeneralSecurityException ("ciphertext too short" );
139
178
}
140
179
141
180
AlgorithmParameterSpec params = getParams (ciphertext , 0 , IV_SIZE_IN_BYTES );
142
- localCipher . get () .init (Cipher .DECRYPT_MODE , keySpec , params );
181
+ cipher .init (Cipher .DECRYPT_MODE , keySpec , params );
143
182
if (associatedData != null && associatedData .length != 0 ) {
144
- localCipher . get () .updateAAD (associatedData );
183
+ cipher .updateAAD (associatedData );
145
184
}
146
- return localCipher
147
- .get ()
148
- .doFinal (ciphertext , IV_SIZE_IN_BYTES , ciphertext .length - IV_SIZE_IN_BYTES );
185
+ return cipher .doFinal (ciphertext , IV_SIZE_IN_BYTES , ciphertext .length - IV_SIZE_IN_BYTES );
149
186
}
150
187
151
188
/**
0 commit comments