如何使用Android的身份验证标记进行GCM加密

时间:2017-06-08 02:49:10

标签: android encryption aes-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;
    }

如何在此代码中插入身份验证标记?

1 个答案:

答案 0 :(得分:3)

您已经包含了身份验证标记。 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很快就会失去较小身份验证标记的安全性,建议不要更改大小,除非您的协议是为其明确设计的。