android 设备和 .Net 服务器上的 AES 加密

AES encryption on android device and on .Net server

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

    // 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 和 IV 都不需要保密,所以你可以简单地将它们与密文一起发送到另一端。通常是密文的前缀,但可以使用任何编码。

代码

下面的示例包括:

  • 2 字节盐大小
  • 2 字节 IV 大小
  • 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。如果不保护完整性,由于 plaintext/padding 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());

您还需要流媒体 class:

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 时不要忘记使用时间静态比较。我对流处理不是很满意,所以尽可能优化。