【问题标题】:Tag Mismatch error when decrypting the key using google tink library使用 google tink 库解密密钥时出现标签不匹配错误
【发布时间】:2019-08-30 05:20:05
【问题描述】:

我是密码学新手。我正在开发一个 poc 来加密和解密一个字符串。当我解密加密的字符串时,它有时会起作用,但有时会引发标签不匹配错误。我错过了什么吗?

这是我的代码:

EncryptionServiceImpl.java

public class EncryptionServiceImpl {
    private static final Logger log = LoggerFactory.getLogger("EncryptionServiceImpl");

    private final KeysetHandle keysetHandle;
    private final Aead aead;

    public EncryptionServiceImpl() throws GeneralSecurityException {
        AeadConfig.register();
        this.keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);
        aead = AeadFactory.getPrimitive(keysetHandle);
    }

    public String encrypt(String text) throws GeneralSecurityException {

        log.info(String.format("Encrypting %s", text));
        byte[] plainText = text.getBytes();
        byte[] additionalData = "masterkey".getBytes();
        byte[] cipherText = aead.encrypt(plainText,additionalData);

        String output = new String(cipherText);

        log.info(String.format("The encrypted text: %s", output));
        return output;
    }

    public String decrypt(String text) throws GeneralSecurityException {

        log.info(String.format("Decrypting %s", text));

        byte[] cipherText = text.getBytes();
        byte[] additionalData = "masterkey".getBytes();
        byte[] decipheredData = aead.decrypt(cipherText,additionalData);

        String output = new String(decipheredData);

        log.info(String.format("The decrypted text: %s", output));
        return output;
    }

}

EncryptionServiceImplTest.java

public class EncryptionServiceImplTest {

    @Test
    public void encrypt() throws IOException, GeneralSecurityException {
        EncryptionServiceImpl encryptionService = new EncryptionServiceImpl();
        String encryptedText = encryptionService.encrypt("Hello World");
        assertThat(encryptedText, Matchers.notNullValue());
    }

    @Test
    public void decrypt() throws IOException, GeneralSecurityException {
        EncryptionServiceImpl encryptionService = new EncryptionServiceImpl();

        String encryptedText = encryptionService.encrypt("Hello World");
        String decrypedText = encryptionService.decrypt(encryptedText);

        assertThat(decrypedText, Matchers.is("Hello World"));
    }
}

例外: 信息:密文前缀匹配密钥,但无法解密:javax.crypto.AEADBadTagException:标签不匹配! com.encryption.api.service.EncryptionServiceImplTest > 解密失败

java.security.GeneralSecurityException at EncryptionServiceImplTest.java:25

解密失败 java.security.GeneralSecurityException:解密失败 在 com.google.crypto.tink.aead.AeadFactory$1.decrypt(AeadFactory.java:109) 在 com.encryption.api.service.EncryptionServiceImpl.decrypt(EncryptionServiceImpl.java:53) 在 com.encryption.api.service.EncryptionServiceImplTest.decrypt(EncryptionServiceImplTest.java:25) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) 在 org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 在 org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) 在 org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 在 org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) 在 org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78) 在 org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57) 在 org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) 在 org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) 在 org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) 在 org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) 在 org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) 在 org.junit.runners.ParentRunner.run(ParentRunner.java:363) 在 org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.runTestClass(JUnitTestClassExecuter.java:114) 在 org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter.execute(JUnitTestClassExecuter.java:57) 在 org.gradle.api.internal.tasks.testing.junit.JUnitTestClassProcessor.processTestClass(JUnitTestClassProcessor.java:66) 在 org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.processTestClass(SuiteTestClassProcessor.java:51) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) 在 org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:32) 在 org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:93) 在 com.sun.proxy.$Proxy1.processTestClass(未知来源) 在 org.gradle.api.internal.tasks.testing.worker.TestWorker.processTestClass(TestWorker.java:108) 在 sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 在 sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) 在 sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 在 java.lang.reflect.Method.invoke(Method.java:498) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:35) 在 org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24) 在 org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:146) 在 org.gradle.internal.remote.internal.hub.MessageHubBackedObjectConnection$DispatchWrapper.dispatch(MessageHubBackedObjectConnection.java:128) 在 org.gradle.internal.remote.internal.hub.MessageHub$Handler.run(MessageHub.java:404) 在 org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:63) 在 org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:46) 在 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) 在 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) 在 org.gradle.internal.concurrent.ThreadFactoryImpl$ManagedThreadRunnable.run(ThreadFactoryImpl.java:55) 在 java.lang.Thread.run(Thread.java:748)

1 个测试完成,1 个失败

【问题讨论】:

    标签: java tink


    【解决方案1】:

    如果加密消息的字节序列存储在字符串中,则必须使用适当的编码。适当意味着编码必须允许序列中的所有字节或字节组合。如果不是这种情况,则字节序列中的值会在存储期间自动更改并且不会被注意到。如果在解密期间从字符串中重建字节数组,则原始字节数组和重建的字节数组不同,并且解密失败。这很好解释here

    由于AES-GCM 为每次加密生成一个新的初始化向量,因此每次加密的加密消息都不同,即使明文相同。

    两者都导致在您的示例中,加密有时有效,有时无效:只要字节序列与您使用的编码兼容,解密就有效,否则无效。

    如果您想独立于编码,只需使用字节数组本身,即encrypt-方法返回字节数组而不是字符串,类似地,传递字节数组而不是字符串到decrypt-方法。

    【讨论】:

    • 感谢您的帮助。按照您共享的链接中的建议添加编码 ISO-8859-1 后,它可以工作。
    • 为了完整起见:如果加密的消息已经存储在字符串中,则还应考虑 Base64 和十六进制编码。两者都将 each 值映射到 ISO-8859-1 不这样做的可读字符(例如,从0x800x9f 的值未分配给 ISO-8859-1 中的任何字符)。有时这很重要。但尽管如此,ISO-8859-1 当然解决了当前的问题。只是提醒一下,可以直接使用字节数组而不是字符串,这样就完全不依赖编码了。
    • 我避免使用字符串并直接处理字节数组和 base64 加密。有时我仍然会看到这个问题...这是我更改的代码:
    • 对于在使用 TestNG 时遇到此问题的每个人:TestNG 将在调用 assertEquals(byte[] ,byte[]) 时将您的 byte[] 数组转换为字符串,这将导致此处描述的问题。
    【解决方案2】:

    我修改了代码,但我仍然看到有时同一请求的解密失败。

    public class Utils {
    private static final Logger log = LoggerFactory.getLogger(Utils.class);
    private Aead aead;
    private static Utils utils;
    
    private Utils() {
        try {
            AeadConfig.register();
            KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES128_GCM);
            aead = AeadFactory.getPrimitive(keysetHandle);
        } catch (GeneralSecurityException e) {
            log.error(String.format("Error occured: %s",e.getMessage())).log();
        }
    }
    
    public static Utils getInstance() {
        if(null == utils) {
            utils = new Utils();
        }
    
        return utils;
    }
    
    public String encrypt(String text) throws GeneralSecurityException, UnsupportedEncodingException {
        byte[] plainText = text.getBytes("ISO-8859-1");
        byte[] additionalData = null;
        byte[] cipherText = aead.encrypt(plainText,additionalData);
    
        String output = Base64.getEncoder().encodeToString(cipherText);
        return output;
    }
    
    public String decrypt(String text) throws GeneralSecurityException, UnsupportedEncodingException {
        byte[] cipherText = Base64.getDecoder().decode(text);
        byte[] additionalData = null;
        byte[] decipheredData = aead.decrypt(cipherText,additionalData);
    
        String output = new String(decipheredData,"ISO-8859-1");
        return output;
    }
    

    }

    【讨论】:

    • 我在当前代码中找不到错误。我改编了旧的 JUnit 测试并测试了新代码。 100 次运行未发生错误。对比:旧代码的错误率约为 50%。所以我无法重现这个问题。您是否可能使用了错误的测试?但是也缺少一些信息来进行更详细的分析,例如新代码的 JUnit 测试是什么样的,使用了哪种纯文本?错误信息是否相同?错误发生的频率如何?
    【解决方案3】:

    当我在本地机器上运行时,我也没有收到代码错误。但是当我部署在 云(谷歌云)我开始看到错误。

    这是我的 Junit 代码:

    public class UtilsTest {
    
    private static final Utils cryptographicUtils = Utils.getInstance();
    
    @Test
    public void encrypt() throws IOException, GeneralSecurityException {
        String encryptedText = cryptographicUtils.encrypt("Hello World");
        assertThat(encryptedText, Matchers.notNullValue());
    }
    
    @Test
    public void decrypt() throws IOException, GeneralSecurityException {
        String encryptedText = cryptographicUtils.encrypt("Hello 123456");
        String decrypedText = cryptographicUtils.decrypt(encryptedText);
    
        assertThat(decrypedText, Matchers.is("Hello 123456"));
    }
    

    }

    【讨论】:

    • 与谷歌云的连接可能是您之前提到的重要信息。可能当前的问题不是加密问题,而是取决于选择的测试环境。尝试以下修改:将Utils-constructor 的访问修饰符从private 更改为public,并在JUnit-test 的encrypt-方法中使用单独的本地Utils-instance 和类似的,a在 JUnit-test 的 decrypt-method 中分别使用 Utils cryptographicUtils = new Utils() 分隔本地 Utils-instance。再次检查问题是否仍然存在。
    • 我也建议不要发送更多问题的答案。相反,您可以编辑旧问题、添加评论或提出新问题。否则会变得混乱。
    • 看起来我将无法从不同的 aead 实例中解密相同的字符串。我修改了代码并公开了 utils 并创建了用于加密的新实例和用于解密的新实例。解密失败并出现解密失败错误。和我在云中看到的一样。这是我的测试代码:@Test public void decrypt() throws IOException, GeneralSecurityException { String encryptedText = new Utils2().encrypt("Hello 123456"); String decrypedText = new Utils2().decrypt(encryptedText); assertThat(decrypedText, Matchers.is("Hello 123456")); }
    • 这可能就是云中发生的事情。我在云中有两个实例,每个实例都有自己的 Aead 实例。一个实例的密文无法在另一个实例中解密
    • 我创建了一个新帖子来澄清我的问题 - *.com/questions/55692186/…