【问题标题】:Java password-based encryption fails on Linux在 Linux 上基于 Java 密码的加密失败
【发布时间】:2017-01-21 20:07:36
【问题描述】:

我需要帮助找出运行 Linux Mint 17.2 Rafaela 的机器上 Java 加密失败的原因。我的应用程序无法使用 RC4 算法解密以前加密的值。

我正在使用 Java 8 u112 进行测试并安装了 JCE,但这并没有帮助。

这是我创建的最小示例,适用于我的 Windows 机器:

import javax.xml.bind.DatatypeConverter;
import java.security.SecureRandom;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

public class CryptoTest
{

  private static final String ADMIN_PASSWORD = "admin";
  private static final String ADMIN_ENCRYPTED_PASSWORD = "532C05C5B5";                             // RC4 encrypted password using KEY
  private static final String ADMIN_AUTH_KEY = "1391a8a860b7d6e2e86df513700e490c16dae47cdae227ca"; // PBKDF2(username,password,salt)
  private static final String CRYPTO_ALGORITHM = "RC4";

  protected static String encryptPassword(String passwordDataToEncrypt, String userskey) throws Exception 
  {
    SecureRandom sr = new SecureRandom(userskey.getBytes());
    KeyGenerator kg = KeyGenerator.getInstance(CRYPTO_ALGORITHM);
    kg.init(sr);
    SecretKey sk = kg.generateKey();
    Cipher cipher = Cipher.getInstance(CRYPTO_ALGORITHM);
    cipher.init(Cipher.ENCRYPT_MODE, sk);
    return bytesToHex(cipher.doFinal(passwordDataToEncrypt.getBytes()));
  }

  private static String bytesToHex(byte[] in) 
  {
    return DatatypeConverter.printHexBinary(in);
  }

  private static byte[] hexStringToByteArray(String s) 
  {
    return DatatypeConverter.parseHexBinary(s);
  }

  protected static String decryptPassword(byte[] toDecryptPassword, String key) throws Exception 
  {
    SecureRandom sr = new SecureRandom(key.getBytes());
    KeyGenerator kg = KeyGenerator.getInstance(CRYPTO_ALGORITHM);
    kg.init(sr);
    SecretKey sk = kg.generateKey();
    Cipher cipher = Cipher.getInstance(CRYPTO_ALGORITHM);
    cipher.init(Cipher.DECRYPT_MODE, sk);
    return new String(cipher.doFinal(toDecryptPassword));
  }

  public static void assertEquals(String arg1, String arg2)
  {
    if (! arg1.equals(arg2))
    {
      System.out.println(String.format("%s does not equal %s", arg1, arg2));
    }
  }

  public static void testGetDecryptedPassword() throws Exception
  {
    String decryptedPassword = decryptPassword(hexStringToByteArray(ADMIN_ENCRYPTED_PASSWORD), ADMIN_AUTH_KEY);
    assertEquals(ADMIN_PASSWORD, decryptedPassword);
  }

  public static void testGetEncryptedPassword() throws Exception
  {
    String encryptedPassword = encryptPassword(ADMIN_PASSWORD, ADMIN_AUTH_KEY);
    assertEquals(ADMIN_ENCRYPTED_PASSWORD, encryptedPassword);
  }

  public static void testEncryptAndDecryptPasswords() throws Exception
  {
    String originalPassword = "password";
    String encryptedPassword = encryptPassword(originalPassword, ADMIN_AUTH_KEY);
    String decryptedPassword = decryptPassword(hexStringToByteArray(encryptedPassword), ADMIN_AUTH_KEY);
    assertEquals(originalPassword, decryptedPassword);

    originalPassword = "This is a STRONG password 4 me!!!@#$^";
    encryptedPassword = encryptPassword(originalPassword, ADMIN_AUTH_KEY);
    decryptedPassword = decryptPassword(hexStringToByteArray(encryptedPassword), ADMIN_AUTH_KEY);
    assertEquals(originalPassword, decryptedPassword);
  }

  public static void main(final String[] args)
  {
    try
    {
      int strength =  Cipher.getMaxAllowedKeyLength("AES");
      if ( strength > 128 ){
        System.out.printf("isUnlimitedSupported=TRUE,strength: %d%n",strength);
      } else {
        System.out.printf("isUnlimitedSupported=FALSE,strength: %d%n",strength);
      }

      testGetDecryptedPassword();
      testGetEncryptedPassword();
      testEncryptAndDecryptPasswords();
    }
    catch (Exception e)
    {
      System.out.printf("Caught exception: %s\n", e.getMessage());
      e.printStackTrace(System.out);
    }
  }
}

我的 linux 机器上的输出是:

isUnlimitedSupported=TRUE,strength: 2147483647
admin does not equal <junk>
532C05C5B5 does not equal 5D16D89D2F
password does not equal <junk>
This is a STRONG password 4 me!!!@#$^ does not equal <junk>

&lt;junk&gt; 是一堆不可打印的字符。

【问题讨论】:

  • 这里有很多不好的东西,尽管我怀疑目前困扰你的是String(byte[]) 构造函数和相关的String.getBytes() 方法。那些使用平台默认字符集,因此是 not 可移植的。始终明确指定字符集。我看到您还使用 SecureRandom 作为 PBKDF。这也是个坏主意。
  • 此外,密码应该被散列(和加盐!),而不是加密。然后,只要您使用相同的盐和哈希方法,您生成的哈希应该与存储的内容匹配。

标签: java encryption


【解决方案1】:

您的代码假定您每次使用以下代码中的相同密码初始化SecureRandom() 时都获得相同的SecretKey

SecureRandom sr = new SecureRandom(userskey.getBytes());
KeyGenerator kg = KeyGenerator.getInstance(CRYPTO_ALGORITHM);
kg.init(sr);
SecretKey sk = kg.generateKey();

您确实不能做出这样的假设,也不应该使用这种方法。 SecureRandomJCA 架构的一部分,当您实例化一个新的 SecureRandom(..) 时,您获得的实际实现取决于您的系统上可用的安全提供程序,以及每个提供程序的优先级。

如果您需要从密码中生成加密密钥,您应该使用为此发明的密钥派生函数,例如 PBKDF2

【讨论】:

  • 我并没有尝试将密码文本用作 PBKDF,而只是想加密一些秘密,存储它,然后稍后再解密。因此,我省略了生成 PBDKF 的代码(在 ADMIN_AUTH_KEY 中显示)。它没有用于身份验证,只是作为一种存储我不想要的纯文本秘密的一种方式。
  • 您正在使用使用密码中的字节初始化的 SecureRandom(..) 生成密钥。 Java 没有定义 SecureRandom 应该如何生成新的随机数 .. 这取决于提供者的实现来决定。根据您当前的提供者如何生成随机数进行加密只会给您带来麻烦..
  • 好的,起初我不明白你的回答或评论,但在回去阅读更多Java文档后,我明白了,你是对的。我找到了一种从这个答案中得到我想要的方法:stackoverflow.com/a/1133815/2174
猜你喜欢
  • 2015-04-21
  • 2010-11-02
  • 1970-01-01
  • 1970-01-01
  • 2012-08-19
  • 1970-01-01
  • 2021-06-21
  • 1970-01-01
  • 2016-03-27
相关资源
最近更新 更多