Как сделать GCM Encrypt с тегом аутентификации для 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;
}
Как вставить тег аутентификации в этот код?
1 ответ
Вы уже включили тег аутентификации. Java (к сожалению - см. Ниже) включает тег аутентификации в зашифрованный текст. Это означает, что он генерируется во время последнего вызова doFinal
,
Вы можете легко проверить это, просмотрев размер зашифрованного текста. Он должен быть того же размера, что и обычный текст (в этом случае aKey
) плюс 128 битов, по умолчанию и разумный размер тега аутентификации.
Это также почему AEADBadTagException
происходит от BadPaddingException
: во время расшифровки проверка происходит неявно. Поэтому старый код все еще будет совместим с режимом GCM.
Как я указывал ранее, я думаю, что включение тега аутентификации в зашифрованный текст является основной ошибкой разработки для API:
- он не позволяет пользователю размещать тег аутентификации где-либо еще - по крайней мере, без манипулирования массивом (размером);
- он удаляет оперативную природу GCM, где расшифровка открытого текста происходит мгновенно (для каждого вызова для обновления), поскольку тег аутентификации должен быть буферизован;
- это делает код больше, менее эффективным и требует ложной буферизации, если местоположение тега аутентификации известно заранее (то есть, если протокол указывает местоположение тега или размер зашифрованного текста напрямую);
По моему мнению, это не отягощает преимущества модернизации Cipher
Класс без добавления методов и поддержания совместимости. Было бы лучше, если бы дизайнер только что добавил методы для извлечения и проверки тега аутентификации отдельно.
Поскольку сейчас идет дождь, я создал пример:
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 быстро теряет безопасность для меньших тегов аутентификации, изменение размера не рекомендуется, если ваш протокол явно не предназначен для этого.