Ответ 1
Нет проверки целостности по этим причинам.
- Потребность не очевидна из прецедента.
-
"AES/GCM/NoPadding"
режим доступен только с Java 7 вперед - Это зависит от пользователя, если они хотят развернуть, например. HMAC и/или AESCMAC (рекомендуется).
- Для этого потребуется дополнительный ключ как минимум, и два полных прохода.
Если у вас есть реализация режима GCM с обеих сторон - например, используя Bouncy Castle на Java 6 - пожалуйста, пойдите для этого, поскольку это намного безопаснее (пока "IV" действительно уникален). Это должно быть очень легко изменить реализацию.
Замечания по внедрению шифрования
- Эта реализация небезопасна при использовании в неограниченной роли клиент/сервер из-за дополнительных атак (требуется 128 попыток на байт или ниже, в среднем, независимо от алгоритма или размера ключа). Вам нужно будет использовать MAC, HMAC или Signature над зашифрованными данными и проверить его перед расшифровкой, чтобы развернуть его в режиме клиент/сервер.
- Decrypt вернет значение null, если дешифрование завершится с ошибкой. Это может указывать только на исключение заполнения, которое должно быть надлежащим образом обработано (предупреждаю ли я об отложенных атаках оракула?)
- Неверные ключи будут возвращены как
InvalidArgumentException
. - Все остальные исключения, связанные с безопасностью, "подметаются под таблицу", так как это означает, что среда выполнения Java недействительна. Например, поддержка поддержки
"UTF-8"
и"AES/CBC/PKCS5Padding"
требуется для каждой реализации Java SE.
Некоторые другие примечания
- Пожалуйста, не пробуйте обратное и вставьте байты непосредственно во входную строку метода шифрования (например, с помощью
new String(byte[])
). Метод может терпеть неудачу! - Оптимизирован для удобства чтения. Перейдите к реализациям Base64 и
CipherStream
, если вы предпочитаете скорость и лучший объем памяти. - Вам нужен минимум Java 6 SE или совместимый для запуска этого кода.
- Шифрование/дешифрование может завершиться с ошибкой для размеров ключей AES более 128 бит, так как вам могут понадобиться файлы политики для неограниченного шифрования (доступные от Oracle)
- Остерегайтесь правительственных правил при экспорте шифрования.
- Эта реализация использует шестнадцатеричные ключи вместо ключей base64, поскольку они достаточно малы, а шестнадцатеричный текст просто упрощается для редактирования/проверки вручную.
- Используемые кодировки/декодирования hex и base64, извлеченные из JDK, не нужны никакие внешние библиотеки.
- Uber прост в использовании, но, конечно, не очень объектно-ориентированный, не кэширование экземпляров объектов, используемых в шифровании/расшифровке. Рефакторинг по желанию.
ОК, вот какой код...
public static String encrypt(final String plainMessage,
final String symKeyHex) {
final byte[] symKeyData = DatatypeConverter.parseHexBinary(symKeyHex);
final byte[] encodedMessage = plainMessage.getBytes(Charset
.forName("UTF-8"));
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
// create the key
final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");
// generate random IV using block size (possibly create a method for
// this)
final byte[] ivData = new byte[blockSize];
final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
rnd.nextBytes(ivData);
final IvParameterSpec iv = new IvParameterSpec(ivData);
cipher.init(Cipher.ENCRYPT_MODE, symKey, iv);
final byte[] encryptedMessage = cipher.doFinal(encodedMessage);
// concatenate IV and encrypted message
final byte[] ivAndEncryptedMessage = new byte[ivData.length
+ encryptedMessage.length];
System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage,
blockSize, encryptedMessage.length);
final String ivAndEncryptedMessageBase64 = DatatypeConverter
.printBase64Binary(ivAndEncryptedMessage);
return ivAndEncryptedMessageBase64;
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(
"key argument does not contain a valid AES key");
} catch (GeneralSecurityException e) {
throw new IllegalStateException(
"Unexpected exception during encryption", e);
}
}
public static String decrypt(final String ivAndEncryptedMessageBase64,
final String symKeyHex) {
final byte[] symKeyData = DatatypeConverter.parseHexBinary(symKeyHex);
final byte[] ivAndEncryptedMessage = DatatypeConverter
.parseBase64Binary(ivAndEncryptedMessageBase64);
try {
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
// create the key
final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");
// retrieve random IV from start of the received message
final byte[] ivData = new byte[blockSize];
System.arraycopy(ivAndEncryptedMessage, 0, ivData, 0, blockSize);
final IvParameterSpec iv = new IvParameterSpec(ivData);
// retrieve the encrypted message itself
final byte[] encryptedMessage = new byte[ivAndEncryptedMessage.length
- blockSize];
System.arraycopy(ivAndEncryptedMessage, blockSize,
encryptedMessage, 0, encryptedMessage.length);
cipher.init(Cipher.DECRYPT_MODE, symKey, iv);
final byte[] encodedMessage = cipher.doFinal(encryptedMessage);
// concatenate IV and encrypted message
final String message = new String(encodedMessage,
Charset.forName("UTF-8"));
return message;
} catch (InvalidKeyException e) {
throw new IllegalArgumentException(
"key argument does not contain a valid AES key");
} catch (BadPaddingException e) {
// you'd better know about padding oracle attacks
return null;
} catch (GeneralSecurityException e) {
throw new IllegalStateException(
"Unexpected exception during decryption", e);
}
}
Использование:
String plain = "Zaphod just zis guy, ya knöw?";
String encrypted = encrypt(plain, "000102030405060708090A0B0C0D0E0F");
System.out.println(encrypted);
String decrypted = decrypt(encrypted, "000102030405060708090A0B0C0D0E0F");
if (decrypted != null && decrypted.equals(plain)) {
System.out.println("Hey! " + decrypted);
} else {
System.out.println("Bummer!");
}