首页 > 解决方案 > 使用具有固定种子的 SecureRandom 对象构建 Cipher 对象是否安全?

问题描述

我的一位同事让我检查他的代码是否足够安全。我看到了一些这样的代码片段:


    private static byte[] encrypt(String plain, String key) throws Exception {
        KeyGenerator kg = KeyGenerator.getInstance("AES");
        SecureRandom secureRandom = new SecureRandom();
        secureRandom.setSeed(key.getBytes());
        kg.init(128, secureRandom);
        SecretKey secretKey = kg.generateKey();
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return cipher.doFinal(plain.getBytes());
    }

    @Test
    public void lcg() throws Throwable {
        String plain = "abc";
        String key = "helloworld";

        byte[] c1 = encrypt(plain, key);
        byte[] c2 = encrypt(plain, key);
        Assert.assertArrayEquals(c1, c2);
    }

encrypt功能用于对敏感数据进行加密,加密后的数据会存入数据库。我认为它首先不起作用,因为 SecureRandom 不会两次生成相同的随机数,即使是由相同的种子初始化,但它只是在测试中起作用。

我认为以这种方式加密某些东西是不安全的,但我不知道这个代码片段有什么问题。

我的问题:

  1. 功能encrypt安全吗?
  2. 如果它不安全,那么这样做有什么问题?

标签: javasecuritycryptography

解决方案


功能encrypt安全吗?

不,对于任何好的安全定义来说,它都是不安全的。

首先,它使用ECB,除非明文块不相关,否则它是不安全的。

更重要的是,new SecureRandom()只需从提供者列表中获取第一个随机数生成器。通常这是"SHA1PRNG"但目前 - 对于 Oracle Java 11 SE 运行时 - 它返回一个更快和更好定义的DRBG. 这些是不兼容的,所以一个密文不能用另一个运行时解密;该代码根本不可移植。

不同的运行时可能会返回完全不同的随机数生成器——可能针对运行时配置进行了优化。这些随机数生成器可能完全取决于给定的种子,如果它是在从中提取随机数之前设置的。它也可能将种子混合状态中。这将产生一个完全随机的密钥,除非你将它保存在某个地方,否则你将永远无法重新生成它。

基本上,这种方法可能由于欧洲央行而既不安全又过于安全——这本身就不是一件小事。您可能永远无法再次解密密文,但您仍然可以区分相同的明文块。


另一个小问题是getBytes使用平台默认编码。这在 Windows (Windows-1252) 和 Linux (UTF-8) 以及 Android 平台(当然也是 UTF-8)之间有所不同。所以在另一个系统上解码明文——如果可以的话——之后你可能仍然会感到惊讶。


这个程序太糟糕了,它应该被归档在圆形垃圾接收器中并实施一些新的东西。为此,至少在 GCM 模式下使用由随机字节和现代密码(如 AES)组成的密钥和 IV 是一个好主意。如果您有密码,您应该使用密码散列(或基于密钥的密钥派生函数),如 PBKDF2 或更现代的一种从中派生密钥。


Kudo 找到了一种更糟糕的方法来从密码中获取密钥。getRawKey很糟糕,但这个更糟。换句话说,你问得很好。


推荐阅读