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 时不要忘记使用时间静态比较。我对流处理不是很满意,所以尽可能优化。
我是密码学界的新手。请原谅,如果这是基本的。在 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 时不要忘记使用时间静态比较。我对流处理不是很满意,所以尽可能优化。