“SecretKeyFactory not available” 是什么意思?

8

这有什么问题吗?

for (Object obj : java.security.Security.getAlgorithms("Cipher")) {
  System.out.println(obj);
}
javax.crypto.SecretKeyFactory.getInstance("AES");

这是输出结果(在Mac OS 10.6上使用JDK 1.6):
BLOWFISH
ARCFOUR
PBEWITHMD5ANDDES
RC2
RSA
PBEWITHMD5ANDTRIPLEDES
PBEWITHSHA1ANDDESEDE
DESEDE
AESWRAP
AES
DES
DESEDEWRAP
PBEWITHSHA1ANDRC2_40

java.security.NoSuchAlgorithmException: AES SecretKeyFactory not available
 at javax.crypto.SecretKeyFactory.<init>(DashoA13*..)
 at javax.crypto.SecretKeyFactory.getInstance(DashoA13*..)
 ...

实际上,当我尝试使用jasypt库时,我遇到了这个异常。我已经在他们的论坛上发布了一个问题:http://forum.jasypt.org/PBEWithMD5AndDES-SecretKeyFactory-not-available-td5051447.html - yegor256
你是否按照此页面上的说明安装了策略文件?http://www.jasypt.org/dependencies.html - jontro
不,我没有。你知道如何将它们添加到Maven项目吗? - yegor256
您需要将它们添加到JDK本身中。 - jontro
实际上,在阅读:http://docs.oracle.com/javase/6/docs/technotes/guides/security/SunProviders.html#SunJCEProvider 时,它说SunJCEProvider是可选的。您需要PBEWithMD5AndDES,并且这是负责提供程序的。 - jontro
显示剩余2条评论
4个回答

7

我在列表中几乎所有算法都遇到了相同的异常(只有DES可以工作)。 - yegor256
该错误现在位于 https://bugs.openjdk.java.net/browse/JDK-7022467。原始版本可在 https://web.archive.org/web/20130430082811/http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=7022467 查看。 - VGR
@VGR,随意编辑答案! - jontro

4

您不一定需要使用SecretKeyFactory。您可以通过以下方式创建AES密钥;

byte[] keyData = ........ 
SecretKeySpec key = new SecretKeySpec(keyData, "AES");

如果您想进行基于密码的加密(PBE),那么只需选择一个安全的哈希算法,使您获得与所需密钥相同大小的哈希值。例如,如果您需要为AES使用256位密钥,这里有一种构建密钥的方法;

private Key buildKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
  MessageDigest digester = MessageDigest.getInstance("SHA-256");
  digester.update(password.getBytes("UTF-8"));
  byte[] key = digester.digest();
  SecretKeySpec spec = new SecretKeySpec(key, "AES");
  return spec;
}

编辑:
我建议除非这是一个玩具项目,否则不要使用MD5和DES,因为它们都有弱点,并且被视为已过时。


4
仅仅对密码进行哈希处理并不是一种安全的生成密码的方式。使用被 JCA 提供的公认密钥派生算法来将文本转换为秘密密钥。 - erickson
@erickson 但是我认为相关问题的最终“评估”(在下面的答案中链接)表明,上述解决方案是AES的推荐解决方案。我们如何改进上述解决方案,而不返回不支持AES的SecretKeyFactory? - Tom
@tom 最终评估不建议使用单轮哈希来执行基于密码的加密。请查看我的答案,以安全地遵循评估的指导。 - erickson

3

并非所有版本的Java默认提供“AES”密钥工厂。

如果您想生成新密钥,请从SecureRandom实例中选择所需的位数(128、192或256),并使用该随机数初始化SecretKeySpec实例。

如果您正在使用基于密码的加密,请创建一个“PBKDF2WithHmacSHA1”算法的SecretKeyFactory,并使用它来初始化SecretKeySpec实例,如这里所示


要注意这种组合 - 在4.4版本中修复的大型Android漏洞意味着前4.4和后4.4会产生不同的结果。http://android-developers.blogspot.ca/2013/12/changes-to-secretkeyfactory-api-in.html - Tom
不要担心PBKDF2WithHmacSHA1;它是你应该使用的算法,并且可以解密使用SunJCE加密的消息。如果你必须处理使用旧版有缺陷的Android加密的数据,那么你就必须注意非标准的8位算法。如果你正在使用SunJCE来解密使用有缺陷的Android实现加密的消息,则需要自己将密码中每个char的上8位设置为零,因为它没有提供具有此行为的算法。 - erickson
我并没有说不要使用它 - 我只是说要小心,由于 Android(以及我相信 BouncyCastle)存在问题,我认为这是有必要的。 - Tom
1
有道理。我强调这一点是因为领先的答案存在一个可怕的安全漏洞,仅使用哈希函数来派生密钥。 - erickson

2

如果您尝试打印所有提供程序,可能会错过您需要的那个。 尝试调用此方法并查看它打印出什么。

for (Object obj : java.security.Security.getProviders()) {
    System.out.println(obj);
}

如果您需要特定的提供程序(例如Bouncy Castle),请将依赖项添加到您的应用程序中,并按以下方式将其添加到安全提供程序中:

java.security.Security.addProvider(new BouncyCastleProvider());

然后再次查看您的提供程序列表。

尝试使用您所有的算法,看看哪些受支持。可以使用jasypt加密进行示例

import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.jasypt.encryption.pbe.config.SimpleStringPBEConfig;
import org.jasypt.exceptions.EncryptionOperationNotPossibleException;
import org.jasypt.registry.AlgorithmRegistry;

使用以下代码:

Set<String> supported = new TreeSet<>();
Set<String> unsupported = new TreeSet<>();
for (Object oAlgorithm : AlgorithmRegistry.getAllPBEAlgorithms()) {
    String algorithm = (String) oAlgorithm;
    try {
        SimpleStringPBEConfig pbeConfig = new SimpleStringPBEConfig();
        pbeConfig.setAlgorithm(algorithm);
        pbeConfig.setPassword("changeme");
        StandardPBEStringEncryptor encryptor = new StandardPBEStringEncryptor();
        encryptor.setConfig(pbeConfig);
        String encrypted = encryptor.encrypt("foo");
        String decrypted = encryptor.decrypt(encrypted);
        supported.add(algorithm);
    } catch (EncryptionOperationNotPossibleException e) {
        unsupported.add(algorithm);
    }
}
System.out.println("Supported");
supported.forEach((String alg) -> System.out.println("   " + alg));
System.out.println("Unsupported");
unsupported.forEach((String alg) -> System.out.println("   " + alg));

我曾经遇到过与Bouncy Castle提供程序类似的问题。将提供程序添加到Java安全性中解决了我的问题。谢谢。java.security.Security.addProvider(new BouncyCastleProvider()); - Lakshmipathi G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接