如何验证GMAC?

时间:2018-01-31 17:23:28

标签: java cryptography aes aes-gcm symmetric

根据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();

其中verifyGmac size == 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();
    }

}

1 个答案:

答案 0 :(得分:1)

是的,这是正确的。 Java API验证MAC,如果失败则抛出异常。它返回加密的纯文本消息,但在您的情况下,消息为空。

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

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

使用静态键和IV当然不行,但我认为这些用于测试目的 - 对吗?