You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何解密PBKDF2与AES/GCM加密数据?Nonce解码问题求助

Phantom钱包导出数据解密问题:Nonce解码失败与Tag mismatch错误

我尝试解密从Phantom钱包导出的如下格式数据(解密密码为123123123):

{
  "encrypted": "Cj7psrZSx4oyzdMHpmemhHT21HrRzQxZ9Qyk9v3g3sg3",
  "nonce": "Gs1UtjfmNJ48rwqNPwfv5MMxcwFYpvtsd",
  "kdf": "pbkdf2",
  "salt": "JHjFEhDPmLFsRVshHYMprr",
  "iterations": 10000,
  "digest": "sha256"
}

编写了如下Java代码解密,但Base64解码nonce时出现错误Last unit does not have enough valid bits,修剪nonce后又出现Tag mismatch错误:

public static void main(String[] args) throws Exception {
    String encrypted = "Cj7psrZSx4oyzdMHpmemhHT21HrRzQxZ9Qyk9v3g3sg3";
    String nonce = "Gs1UtjfmNJ48rwqNPwfv5MMxcwFYpvtsd";
    String salt = "JHjFEhDPmLFsRVshHYMprr";

    SecretKey key = getAESKeyFromPassword("123123123".toCharArray(), salt.getBytes());
    byte[] encryptedBytes = Base64.getDecoder().decode(encrypted);
    byte[] nonceBytes = Base64.getDecoder().decode(nonce);
    String decrypted = decryptAES(encryptedBytes, key, nonceBytes);
}

private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeySpecException {
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    KeySpec spec = new PBEKeySpec(password, salt, 10000, 256);
    return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
}

private static String decryptAES(byte[] cText, SecretKey secret, byte[] nonce) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
    cipher.init(Cipher.DECRYPT_MODE, secret, new GCMParameterSpec(128, nonce));
    byte[] plainText = cipher.doFinal(cText);
    return new String(plainText, UTF_8);
}

请问该如何处理这个nonce?


问题分析与解决方案

1. Nonce解码失败的核心原因

Phantom钱包对nonce、encrypted等字段使用的是无填充的Base64URL编码,而你代码中用的Base64.getDecoder()是标准Base64解码器,它要求数据必须是4的倍数长度(不足时用=填充),因此会因缺少填充字符报错。

2. Tag mismatch的触发原因

修剪nonce会直接破坏其完整性,导致AES-GCM模式的认证标签验证失败;另外还要注意:

  • Phantom的encrypted字段包含了密文+GCM认证标签,无需拆分,只需正确解码
  • KDF参数(迭代次数、密钥长度、哈希算法)必须和导出数据完全匹配,否则生成的密钥错误也会引发标签不匹配

3. 修正后的完整代码

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class PhantomDecryptor {
    public static void main(String[] args) throws Exception {
        String encrypted = "Cj7psrZSx4oyzdMHpmemhHT21HrRzQxZ9Qyk9v3g3sg3";
        String nonce = "Gs1UtjfmNJ48rwqNPwfv5MMxcwFYpvtsd";
        String salt = "JHjFEhDPmLFsRVshHYMprr";
        String password = "123123123";

        // 使用Base64URL解码器,自动处理无填充的编码格式
        Base64.Decoder urlDecoder = Base64.getUrlDecoder();
        byte[] encryptedBytes = urlDecoder.decode(encrypted);
        byte[] nonceBytes = urlDecoder.decode(nonce);
        byte[] saltBytes = salt.getBytes(StandardCharsets.UTF_8); // 明确指定UTF-8编码,避免平台差异

        SecretKey key = getAESKeyFromPassword(password.toCharArray(), saltBytes);
        String decrypted = decryptAES(encryptedBytes, key, nonceBytes);
        System.out.println("解密结果:" + decrypted);
    }

    private static SecretKey getAESKeyFromPassword(char[] password, byte[] salt) throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        // 严格匹配Phantom导出的KDF参数:10000次迭代、256位密钥
        PBEKeySpec spec = new PBEKeySpec(password, salt, 10000, 256);
        return new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    }

    private static String decryptAES(byte[] cText, SecretKey secret, byte[] nonce) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        // GCM标签长度固定128位,和Phantom的加密配置一致
        GCMParameterSpec spec = new GCMParameterSpec(128, nonce);
        cipher.init(Cipher.DECRYPT_MODE, secret, spec);
        byte[] plainText = cipher.doFinal(cText);
        return new String(plainText, StandardCharsets.UTF_8);
    }
}

关键修正点

  • 将标准Base64解码器替换为Base64.getUrlDecoder(),适配Phantom的无填充Base64URL编码
  • 明确指定字符串转字节数组时的编码为UTF_8,避免不同平台默认编码差异导致的错误
  • 确保KDF参数(迭代次数、密钥长度)和导出数据完全一致,保证密钥生成正确

内容的提问来源于stack exchange,提问作者Alexander Prag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 11:22:49