如何使用 Android 的身份验证标记进行 GCM 加密
How to make GCM Encrypt with authentication tag for Android
我想通过 GCM 模式使用 Android 的身份验证标记使函数加密数据。
这是我的源代码:
public static byte[] GCMEncrypt(String hexKey, String hexIV, byte[] aad) throws Exception {
byte[] aKey = hexStringToByteArray(hexKey);
byte[] aIV = hexStringToByteArray(hexIV);
Key key = new SecretKeySpec(aKey, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(16 * Byte.SIZE, aIV));
cipher.updateAAD(aad);
byte[] encrypted = cipher.doFinal(aKey);
return encrypted;
}
如何在此代码中插入身份验证标记?
您已经添加了身份验证标签。 Java(不幸的是 - 见下文)在密文中包含身份验证标签。这意味着它是在最后一次调用 doFinal
.
期间生成的
您可以通过查看密文的大小轻松地检查这一点。它应该与明文大小相同(在本例中为 aKey
)加上 128 位,即身份验证标记的默认和合理大小。
这也是 AEADBadTagException
派生自 BadPaddingException
的原因:在解密过程中,验证发生 隐式 。所以旧代码仍然与 GCM 模式兼容。
正如我之前指出的,我认为在密文中包含身份验证标签是 API:
的主要设计错误
- 它不允许用户将身份验证标签放在其他地方——至少在没有数组(大小)操作的情况下;
- 它消除了 GCM 的 在线 性质,其中明文解密是即时的(对于每次更新调用),因为需要缓冲身份验证标记;
- 如果事先知道身份验证标签的位置(即如果协议直接指定标签位置或密文的大小),它会使代码更大、效率更低并且需要虚假缓冲;
在我看来,这并不能抵消在不添加方法和保持兼容性的情况下改造 Cipher
class 的优势。如果设计者能单独添加检索和验证身份验证标签的方法就更好了。
因为目前正在下雨,所以我创建了一个示例:
public static void main(String... args) throws Exception {
int tagSize = 96;
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
SecretKey aesKey = new SecretKeySpec(new byte[16], "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(tagSize, new byte[gcm.getBlockSize()]);
gcm.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
byte[] pt = "Maarten Bodewes creates code".getBytes(StandardCharsets.UTF_8);
System.out.println(pt.length);
byte[] ctAndTag = new byte[gcm.getOutputSize(pt.length)];
System.out.println(ctAndTag.length);
int off = 0;
off += gcm.update(pt, 0, pt.length, ctAndTag, off);
// prints 16 (for the Oracle crypto provider)
// meaning it is not online, buffering even during encryption
System.out.println(off);
off += gcm.doFinal(new byte[0], 0, 0, ctAndTag, off);
// prints 40 for the Oracle crypto provider, meaning it doesn't *just*
// output the tag during doFinal !
System.out.println(off);
int ctSize = ctAndTag.length - tagSize / Byte.SIZE;
System.out.println(ctSize);
byte[] ct = Arrays.copyOfRange(ctAndTag, 0, ctSize);
byte[] tag = Arrays.copyOfRange(ctAndTag, ctSize, ctAndTag.length);
System.out.println(Hex.toHexString(ct));
System.out.println(Hex.toHexString(tag));
}
备注:
- 用自身加密密钥没有意义,我希望这只是为了演示目的
- 对于较小的身份验证标签,GCM 会很快失去安全性,建议不要更改大小,除非您的协议是专门为此设计的。
我想通过 GCM 模式使用 Android 的身份验证标记使函数加密数据。 这是我的源代码:
public static byte[] GCMEncrypt(String hexKey, String hexIV, byte[] aad) throws Exception {
byte[] aKey = hexStringToByteArray(hexKey);
byte[] aIV = hexStringToByteArray(hexIV);
Key key = new SecretKeySpec(aKey, "AES");
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key, new GCMParameterSpec(16 * Byte.SIZE, aIV));
cipher.updateAAD(aad);
byte[] encrypted = cipher.doFinal(aKey);
return encrypted;
}
如何在此代码中插入身份验证标记?
您已经添加了身份验证标签。 Java(不幸的是 - 见下文)在密文中包含身份验证标签。这意味着它是在最后一次调用 doFinal
.
您可以通过查看密文的大小轻松地检查这一点。它应该与明文大小相同(在本例中为 aKey
)加上 128 位,即身份验证标记的默认和合理大小。
这也是 AEADBadTagException
派生自 BadPaddingException
的原因:在解密过程中,验证发生 隐式 。所以旧代码仍然与 GCM 模式兼容。
正如我之前指出的,我认为在密文中包含身份验证标签是 API:
的主要设计错误- 它不允许用户将身份验证标签放在其他地方——至少在没有数组(大小)操作的情况下;
- 它消除了 GCM 的 在线 性质,其中明文解密是即时的(对于每次更新调用),因为需要缓冲身份验证标记;
- 如果事先知道身份验证标签的位置(即如果协议直接指定标签位置或密文的大小),它会使代码更大、效率更低并且需要虚假缓冲;
在我看来,这并不能抵消在不添加方法和保持兼容性的情况下改造 Cipher
class 的优势。如果设计者能单独添加检索和验证身份验证标签的方法就更好了。
因为目前正在下雨,所以我创建了一个示例:
public static void main(String... args) throws Exception {
int tagSize = 96;
Cipher gcm = Cipher.getInstance("AES/GCM/NoPadding");
SecretKey aesKey = new SecretKeySpec(new byte[16], "AES");
GCMParameterSpec gcmSpec = new GCMParameterSpec(tagSize, new byte[gcm.getBlockSize()]);
gcm.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
byte[] pt = "Maarten Bodewes creates code".getBytes(StandardCharsets.UTF_8);
System.out.println(pt.length);
byte[] ctAndTag = new byte[gcm.getOutputSize(pt.length)];
System.out.println(ctAndTag.length);
int off = 0;
off += gcm.update(pt, 0, pt.length, ctAndTag, off);
// prints 16 (for the Oracle crypto provider)
// meaning it is not online, buffering even during encryption
System.out.println(off);
off += gcm.doFinal(new byte[0], 0, 0, ctAndTag, off);
// prints 40 for the Oracle crypto provider, meaning it doesn't *just*
// output the tag during doFinal !
System.out.println(off);
int ctSize = ctAndTag.length - tagSize / Byte.SIZE;
System.out.println(ctSize);
byte[] ct = Arrays.copyOfRange(ctAndTag, 0, ctSize);
byte[] tag = Arrays.copyOfRange(ctAndTag, ctSize, ctAndTag.length);
System.out.println(Hex.toHexString(ct));
System.out.println(Hex.toHexString(tag));
}
备注:
- 用自身加密密钥没有意义,我希望这只是为了演示目的
- 对于较小的身份验证标签,GCM 会很快失去安全性,建议不要更改大小,除非您的协议是专门为此设计的。