【问题标题】:AES encryption on android device and on .Net serverAndroid 设备和 .Net 服务器上的 AES 加密
【发布时间】:2026-01-18 20:50:02
【问题描述】:

我是密码学领域的新手。请原谅,如果这是基本的。在 Android 方面,我正在尝试使用以下代码 sn-p 进行加密:

    // Get the salt
    SecureRandom random = new SecureRandom();
    byte[] salt = new byte[saltLength];
    random.nextBytes(salt);

    // Secret key
    SecretKey secretKey = getSecretKey(seed, salt);

    // Get Cipher instance for AES with Padding algorithm PKCS5
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

    // Initialization vector, as CBC requires IV
    byte[] iv = new byte[cipher.getBlockSize()];
    random.nextBytes(iv);

    // Algorithm spec for IV
    IvParameterSpec ivParams = new IvParameterSpec(iv);

    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParams);

    // Encrypt the text
    byte[] encryptedTextInBytes = cipher.doFinal(textToBeEncrypted
            .getBytes("UTF-8"));

    return Base64Encoder.encode(encryptedTextInBytes);

我的问题是如何在服务器端解密,因为盐是完全随机的。初始化向量(IV)也会出现同样的问题。有任何想法吗?这可以通过为 IV 和 salt 设置硬编码数字来解决,但这违背了安全的目的。

【问题讨论】:

  • 你也可以显示getSecretKey 吗?它可能无法按预期工作。

标签: android .net encryption cross-platform aes


【解决方案1】:

salt 或 IV 都不需要保密,因此您可以简单地将它们与密文一起发送给对方。通常在密文前面加上前缀,但可以使用任何编码。

代码

下面的例子包括:

  • 2 字节盐大小
  • 2 字节 IV 大小
  • 4 字节密文大小
  • 密文

ByteArrayOutputStream blob = new ByteArrayOutputStream();
DataOutputStream dataBlob = new DataOutputStream(blob);

// Get the salt
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);

dataBlob.writeShort(saltLength);
dataBlob.write(salt);

// Secret key
SecretKey secretKey = getSecretKey(seed, salt);

// Get Cipher instance for AES with Padding algorithm PKCS5
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

// Initialization vector, as CBC requires IV
byte[] iv = new byte[cipher.getBlockSize()];
random.nextBytes(iv);

dataBlob.write(iv.length);
dataBlob.write(iv);

// Algorithm spec for IV
IvParameterSpec ivParams = new IvParameterSpec(iv);

cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParams);

// Encrypt the text
byte[] encryptedTextInBytes = cipher.doFinal(textToBeEncrypted
        .getBytes(StandardCharsets.UTF_8));

dataBlob.writeInt(encryptedTextInBytes.length);
dataBlob.write(encryptedTextInBytes);


// out of scope: add HMAC protection over current contents of blob here
// (or while writing it to dataBlob, also update a HMAC) 

// Base64Encoder encode;
return Base64Encoder.encode(blob.toByteArray());

保护

为了安全起见,您需要添加完整性保护和身份验证。这意味着在盐、IV 和密文的(编码)字节上应用 HMAC。如果完整性没有得到保护,由于明文/填充 oracle 攻击,这种方案甚至可能不利于机密性。

例如:

public static String encrypt(int saltLength, byte[] seed, String textToBeEncrypted) throws Exception {

Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Mac hmac = Mac.getInstance("HmacSHA256");

// Get the salt
SecureRandom random = new SecureRandom();
byte[] salt = new byte[saltLength];
random.nextBytes(salt);

// Secret key
SecretKey secretKey = getSecretKey(seed, salt);

// Initialization vector, as CBC requires IV
byte[] iv = new byte[cipher.getBlockSize()];
random.nextBytes(iv);

// Algorithm spec for IV
IvParameterSpec ivParams = new IvParameterSpec(iv);

// Get Cipher instance for AES with Padding algorithm PKCS5
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParams);
hmac.init(secretKey);

ByteArrayOutputStream blob = new ByteArrayOutputStream();
MacOutputStream macStream = new MacOutputStream(blob, hmac);
DataOutputStream dataBlob = new DataOutputStream(macStream);

dataBlob.writeShort(saltLength);
dataBlob.write(salt);
dataBlob.write(iv.length);
dataBlob.write(iv);

// Encrypt the text
byte[] encryptedTextInBytes = cipher.doFinal(textToBeEncrypted
        .getBytes(StandardCharsets.UTF_8));

dataBlob.writeInt(encryptedTextInBytes.length);
dataBlob.write(encryptedTextInBytes);

dataBlob.writeShort(hmac.getMacLength());
dataBlob.write(macStream.getMac());

return Base64Encoder encode(blob.toByteArray());

你也需要流媒体类:

public class MacOutputStream extends FilterOutputStream {

    private final Mac mac;

    public MacOutputStream(OutputStream out, Mac mac) {
        super(out);
        this.mac = mac;
    }

    @Override
    public void write(byte[] b) throws IOException {
        mac.update(b);
        out.write(b);
    }

    @Override
    public void write(int b) throws IOException {
        mac.update((byte) b);
        out.write(b);
    }

    public byte[] getMac() {
        return mac.doFinal();
    }
}

请注意,最好使用单独的密钥,并且在验证 MAC 时不要忘记使用时间静态比较。我对流处理并不完全满意,因此请尽可能优化。

【讨论】:

  • 谢谢。如果可以的话,你能给我指向一个添加 HMAC 的链接吗?
  • 添加了代码来庆祝我的第 1000 个加密点。从现在开始,加密帖子最好成为主题:)