如何验证GMAC?

How to verify a GMAC?

根据Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC的第5.2节(两个GCM函数),其中提到对于GMAC的情况,经过身份验证的加密和解密函数成为生成和验证非机密数据上的身份验证标签

上网查JCA参考页,了解到GMAC是这样生成的:

Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
cipher.init(ENCRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
cipher.updateAAD(aadData);
byte[] gmac = cipher.doFinal();

但是,我对GMAC验证有疑问。这是验证 GMAC 的正确方法吗?

Cipher decryptCipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
decryptCipher.init(DECRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
decryptCipher.updateAAD(aadData);
decryptCipher.update(gmac);
byte[] verifiedGmac = decryptCipher.doFinal();

其中已验证 Gmac 大小 == 0?

这是包含输入和输出的完整代码:

import org.hamcrest.core.Is;
import org.junit.Test;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.SecureRandom;

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static javax.xml.bind.DatatypeConverter.parseHexBinary;
import static javax.xml.bind.DatatypeConverter.printHexBinary;
import static org.junit.Assert.assertThat;

public class TestGmac {

    @Test
    public void generateAndVerifyGmac() throws Exception {
        final byte[] message = parseHexBinary("AAAAAAAAAAAA");
        final byte[] authenticatedKey = parseHexBinary("63509E5A672C092CAD0B1DC6CE009A61");
        final byte[] aadData = buildAadForAuthenticationOnly(message, authenticatedKey);
        final byte[] iv = parseHexBinary("BBBBBBBBBBBBBBBBBBBBBBBB");
        SecretKeySpec encryptionKey = new SecretKeySpec(parseHexBinary("55804F3AEB4E914DC91255944A1F565A"), "AES");

        // Generate GMAC
        Cipher cipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
        cipher.init(ENCRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
        cipher.updateAAD(aadData);
        byte[] gmac = cipher.doFinal();
        assertThat(printHexBinary(gmac), Is.is("44C955D63799428524E97993"));

        // Verify GMAC
        Cipher decryptCipher = Cipher.getInstance("AES/GCM/PKCS5Padding");
        decryptCipher.init(DECRYPT_MODE, encryptionKey, new GCMParameterSpec(96, iv), new SecureRandom());
        decryptCipher.updateAAD(aadData);
        decryptCipher.update(gmac);
        byte[] verifiedGmac = decryptCipher.doFinal();
        assertThat(printHexBinary(verifiedGmac), Is.is(""));
    }

    private byte[] buildAadForAuthenticationOnly(byte[] message, byte[] authenticatedKey) throws IOException {
        ByteArrayOutputStream aaDoutputStream = new ByteArrayOutputStream();
        aaDoutputStream.write(parseHexBinary("10"));
        aaDoutputStream.write(authenticatedKey);
        aaDoutputStream.write(message);
        return aaDoutputStream.toByteArray();
    }

}

是的,没错。 Java API 验证 MAC,如果失败则抛出异常。它 returns 加密的明文消息,但在您的情况下消息是空的。

创建 GCM 有两种设计选择 API:单独处理身份验证标签或假设它是密文的一部分。虽然我更喜欢单独处理密文和身份验证标签,但 Java/Oracle 决定保留 AEAD RFC 并将身份验证标签包含在密文中(需要额外的缓冲,代码大小增加约 30%,并消除了执行在线解密)。

因此,尽管您的代码可能感觉有点奇怪,但 MAC 的处理似乎没问题 - 如果不是这样,您会在 doFinal 的调用中得到 AEADBadTagException解密过程中。


使用静态密钥和 IV 肯定不行,但我认为它们是用于测试目的 - 对吗?