Android AES 基于密码的加密,对每条消息使用一个密钥和随机 IV

发布于 2025-01-08 00:26:32 字数 2648 浏览 5 评论 0原文

受这篇文章的启发,我目前正在 Android 上使用 AES 256 实现对称加密/解密: Java 256 位 AES 加密。 我实现的目的是我想对数据库中的数据进行加密。

对于密钥生成,我使用以下构造函数,该构造函数采用 char[] 密码:

public Cryptography(char[] password) throws NoSuchAlgorithmException,
        InvalidKeySpecException, NoSuchPaddingException {

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
    KeySpec spec = new PBEKeySpec(password, salt, 1024, 256);
    secretKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    cipher = Cipher.getInstance(AES/CBC/PKCS5Padding);
}

因此,当我在 Android 中启动 Activity 时,我会初始化 Cryptography 类的新实例,从而获得生成的密钥。 salt 是 16 字节的固定随机 byte[]。这意味着我总是得到相同的密钥。至于原因,稍后再说。

现在,当我在一个 Activity 中获得一个对象后,我可以使用以下加密和解密方法并始终使用相同的密钥:

public byte[] encrypt(String cleartext) throws InvalidKeyException,
        IllegalBlockSizeException, BadPaddingException,
        UnsupportedEncodingException, InvalidParameterSpecException {

    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    byte[] encText = cipher.doFinal(cleartext.getBytes(CHARSET_NAME));
    byte[] iv = cipher.getParameters()
            .getParameterSpec(IvParameterSpec.class).getIV();

    byte[] enc = new byte[IV_SIZE + encText.length];

    for (int i = 0; i < enc.length; i++) {
        if (i < IV_SIZE)
            enc[i] = iv[i];
        else if (i < enc.length)
            enc[i] = encText[i - IV_SIZE];
    }

    return enc;
}

public String decrypt(byte[] encryptedText) throws InvalidKeyException,
        InvalidAlgorithmParameterException, UnsupportedEncodingException,
        IllegalBlockSizeException, BadPaddingException {

    byte[] iv = new byte[IV_SIZE];
    byte[] dec = new byte[encryptedText.length - IV_SIZE];

    for (int i = 0; i < encryptedText.length; i++) {
        if (i < IV_SIZE)
            iv[i] = encryptedText[i];
        else if (i < encryptedText.length)
            dec[i - IV_SIZE] = encryptedText[i];
    }

    cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));

    return new String(cipher.doFinal(dec), CHARSET_NAME);
}

如您所见,每次加密消息时,我都会保存一个新的 IV 以及密文。

结论:我对数据库表中的每个字段使用一个加密密钥、一个随机盐和一个新的 IV。

首先,每次加密数据库表中的一个字段时,我想生成一个带有新盐和新 IV 的新密钥,并将所需的盐和 IV 与密文一起保存,或至少为一个表行保存。但我之所以这样做,是因为在 Android 设备上生成密钥需要很长时间。我在模拟器上进行了测试,但生成密钥大约需要两秒钟。这就是为什么我在活动启动时只生成一个密钥的原因。

最后我的问题是: 按照我的方法,仅使用一个密钥但为每条消息使用新鲜的随机 IV 是否足够安全?目前,我没有看到其他方法可以通过保持其与性能的平衡来使其尽可能安全。

我希望我写的内容足够清楚,有人可以就此给我一些建议。

亲切的问候

xoidberg

I'm currently implementing a symmetric en-/decryption using AES 256 on Android, inspired by this post:
Java 256bit AES Encryption.
The purpose of my implementation is that I want to encrypt the data in a database.

For key generation I use the following constructor which takes a char[] password:

public Cryptography(char[] password) throws NoSuchAlgorithmException,
        InvalidKeySpecException, NoSuchPaddingException {

    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC");
    KeySpec spec = new PBEKeySpec(password, salt, 1024, 256);
    secretKey = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
    cipher = Cipher.getInstance(AES/CBC/PKCS5Padding);
}

So when I start my Activity in Android I initialize a new instance of my Cryptography class and therefore get a generated key. The salt is a fixed random byte[] of 16 bytes. So that means that I always get the same key. The reason for that later.

Now after I got an object in one Activity I can use the following encrypt and decrypt methods with always the same key:

public byte[] encrypt(String cleartext) throws InvalidKeyException,
        IllegalBlockSizeException, BadPaddingException,
        UnsupportedEncodingException, InvalidParameterSpecException {

    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    byte[] encText = cipher.doFinal(cleartext.getBytes(CHARSET_NAME));
    byte[] iv = cipher.getParameters()
            .getParameterSpec(IvParameterSpec.class).getIV();

    byte[] enc = new byte[IV_SIZE + encText.length];

    for (int i = 0; i < enc.length; i++) {
        if (i < IV_SIZE)
            enc[i] = iv[i];
        else if (i < enc.length)
            enc[i] = encText[i - IV_SIZE];
    }

    return enc;
}

public String decrypt(byte[] encryptedText) throws InvalidKeyException,
        InvalidAlgorithmParameterException, UnsupportedEncodingException,
        IllegalBlockSizeException, BadPaddingException {

    byte[] iv = new byte[IV_SIZE];
    byte[] dec = new byte[encryptedText.length - IV_SIZE];

    for (int i = 0; i < encryptedText.length; i++) {
        if (i < IV_SIZE)
            iv[i] = encryptedText[i];
        else if (i < encryptedText.length)
            dec[i - IV_SIZE] = encryptedText[i];
    }

    cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));

    return new String(cipher.doFinal(dec), CHARSET_NAME);
}

As you can see, I save a fresh new IV along with the ciphertext everytime I encrypt a message.

In conclusion: I use ONE encryption key, ONE random salt and a new IV for EVERY field in a database table.

First I wanted to generate a new key with a new salt and a new IV everytime I encrypt ONE field in the database table and save the required salt and IV with along with the ciphertext, or at least for one table row. But the reason why I did it like above mentioned is, because generating a key on an Android device takes to much time. I tested in on an emulator, but it took about two seconds for generating a key. This is why I just generated one key when an Activity is started.

So finally my question:
With my approach, is it secure enough by using just one key, but fresh random IV's for every message? Currently, I don't see another way to make it as secure as possible by keeping it in balance with performance.

I hope it is clear enough what I wrote and somebody could give me some advice on that.

Kind Regards

xoidberg

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

晨曦慕雪 2025-01-15 00:26:32

我相信这个问题与您(xoidberg)无关,但可能与其他一些人相关。

据我了解 - 您使用盐从密码创建一个(安全随机)密钥。如果每个用户都有一个随机的(不同的)盐 - 那就可以了。否则可能会出现问题。

我相信这就是你所做的,所以(对我来说)似乎没问题。

我只想提一下,当您保存某些值(通常是密码)的哈希函数时,通常需要使用盐。 MD5 或 SHA 等哈希函数没有密钥,因此您必须为此添加随机性。这就是为什么你需要盐,这就是为什么在这种情况下你通常需要为每个值随机盐(如果你只是用相同的盐保存密码哈希值,人们可以检测到最常见的哈希值并了解用户的密码最常见的哈希值是 123456)。就您而言 - 每个用户都需要独特的盐。

关于 IV - 你真的每次都需要一个随机的(所以没关系)。

I believe the question is not relevant for you (xoidberg), but it might be relevant for some other people.

From what I understand - you use the salt to create a (securely random) key from password. If every user has a random (different) salt - it is ok. Otherwise it might be problematic.

I believe that this is what you did, so it seems (to me) to be ok.

I just want to mention that usually you want to use salts when you save hash function of some values (usually password). Hash functions like MD5 or the SHAs do not have a key, and you must add randomness for this purpose. This is why you need the salt, and this is why in this case you usually need random salt for each value (if you just save passwords hashes with the same salt, one can detect the most common hashes and learn that the password of the users with the most common hash is 123456). In your case - every user needs a unique salt.

About the IV - you really need a random one each time (so it's ok).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文