Как сделать 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 быстро теряет безопасность для меньших тегов аутентификации, изменение размера не рекомендуется, если ваш протокол явно не предназначен для этого.
Другие вопросы по тегам