如何将字符串转换为SecretKey

10

我想将字符串转换为SecretKey。

public void generateCode(String keyStr){ 
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128); // 192 and 256 bits may not be available
// Generate the secret key specs.
secretKey skey=keyStr;  //How can I make the casting here
//SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
}

我尝试使用BASE64Decoder替代secretKey,但我面临一个问题,即我无法指定密钥长度。

编辑:我想从另一个地方调用此函数

 static public String encrypt(String message , String key , int keyLength) throws Exception {
     // Get the KeyGenerator
   KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(keyLength); // 192 and 256 bits may not be available
    // Generate the secret key specs.
     SecretKey skey = key; //here is the error
   byte[] raw = skey.getEncoded();
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    // Instantiate the cipher
    Cipher cipher = Cipher.getInstance("AES");

    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    System.out.println("msg is" + message + "\n raw is" + raw);
    byte[] encrypted = cipher.doFinal(message.getBytes());
    String cryptedValue = new String(encrypted);
    System.out.println("encrypted string: " + cryptedValue);
    return cryptedValue;
}

如果有人能帮忙的话,我将非常感激。


KeyGenerator kgen 从未被使用过。它在那里有什么作用? KeyGenerator 用于选择一个新的随机密钥。但是看起来你想重用一个已经存储在 String 中的现有密钥(可能是Base-64编码的)。 - erickson
2个回答

40

由于以下特定原因,没有完整性检查

  1. 从用例中看不出需要。
  2. "AES/GCM/NoPadding" 模式仅在Java 7及以上版本中可用。
  3. 是否部署 HMAC 和/或 AESCMAC(建议)取决于用户。
  4. 这将需要至少一个额外的密钥和两个完全的传递。

如果你在双方都使用 GCM 模式的实现——例如在 Java 6 上使用 Bouncy Castle,请尽管使用它,因为它更加安全(只要“IV”确实是唯一的)。更改实现应该非常容易。

有关加密的实现注意事项

  1. 当在不受限制的客户端/服务器角色中使用时,此实现不安全,因为存在填充奇偶校验攻击(平均每字节需要 128 次尝试或更少,与算法或密钥大小无关)。在解密之前,您需要在加密数据上使用 MAC、HMAC 或 Signature,并验证它以部署在客户端/服务器模式下。
  2. 如果解密失败,则 Decrypt 将返回 null。这只能指示填充异常,应该足够处理它(我警告过填充奇偶校验攻击了吗?)
  3. 无效的密钥将返回为 InvalidArgumentException
  4. 所有其他与安全相关的异常都“被扫除在地”,因为这意味着 Java 运行时环境无效。例如,支持 "UTF-8""AES/CBC/PKCS5Padding" 对于每个 Java SE 实现都是必需的

一些其他注意事项

  1. 请不要尝试相反的操作,直接将字节插入到加密方法的输入字符串中(例如使用 new String(byte[]))。该方法可能会悄然失败!
  2. 针对可读性进行了优化。如果您更喜欢速度和更好的内存占用率,请选择 Base64 流和 CipherStream 实现。
  3. 您需要至少 Java 6 SE 或兼容版本才能运行此代码。
  4. 对于 AES 密钥大小超过 128 位的加密/解密,可能会失败,因为您可能需要无限制的加密策略文件(可从 Oracle 获得)。
  5. 在出口加密时要注意政府法规。
  6. 此实现使用十六进制密钥而不是 base64 密钥,因为它们足够小,并且十六进制更容易手动编辑/验证。
  7. 使用从 JDK 检索的十六进制和 base64 编码/解码,没有任何外部库。
  8. 非常简单易用,但当然不是很面向对象,在 encrypt/decrypt 中未使用对象实例的缓存。随意重构。

好的,这里有一些代码...

    public static String encrypt(final String plainMessage,
            final String symKeyHex) {
        final byte[] symKeyData = DatatypeConverter.parseHexBinary(symKeyHex);

        final byte[] encodedMessage = plainMessage.getBytes(Charset
                .forName("UTF-8"));
        try {
            final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            final int blockSize = cipher.getBlockSize();

            // create the key
            final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

            // generate random IV using block size (possibly create a method for
            // this)
            final byte[] ivData = new byte[blockSize];
            final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
            rnd.nextBytes(ivData);
            final IvParameterSpec iv = new IvParameterSpec(ivData);

            cipher.init(Cipher.ENCRYPT_MODE, symKey, iv);

            final byte[] encryptedMessage = cipher.doFinal(encodedMessage);

            // concatenate IV and encrypted message
            final byte[] ivAndEncryptedMessage = new byte[ivData.length
                    + encryptedMessage.length];
            System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
            System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage,
                    blockSize, encryptedMessage.length);

            final String ivAndEncryptedMessageBase64 = DatatypeConverter
                    .printBase64Binary(ivAndEncryptedMessage);

            return ivAndEncryptedMessageBase64;
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException(
                    "key argument does not contain a valid AES key");
        } catch (GeneralSecurityException e) {
            throw new IllegalStateException(
                    "Unexpected exception during encryption", e);
        }
    }

    public static String decrypt(final String ivAndEncryptedMessageBase64,
            final String symKeyHex) {
        final byte[] symKeyData = DatatypeConverter.parseHexBinary(symKeyHex);

        final byte[] ivAndEncryptedMessage = DatatypeConverter
                .parseBase64Binary(ivAndEncryptedMessageBase64);
        try {
            final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            final int blockSize = cipher.getBlockSize();

            // create the key
            final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

            // retrieve random IV from start of the received message
            final byte[] ivData = new byte[blockSize];
            System.arraycopy(ivAndEncryptedMessage, 0, ivData, 0, blockSize);
            final IvParameterSpec iv = new IvParameterSpec(ivData);

            // retrieve the encrypted message itself
            final byte[] encryptedMessage = new byte[ivAndEncryptedMessage.length
                    - blockSize];
            System.arraycopy(ivAndEncryptedMessage, blockSize,
                    encryptedMessage, 0, encryptedMessage.length);

            cipher.init(Cipher.DECRYPT_MODE, symKey, iv);

            final byte[] encodedMessage = cipher.doFinal(encryptedMessage);

            // concatenate IV and encrypted message
            final String message = new String(encodedMessage,
                    Charset.forName("UTF-8"));

            return message;
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException(
                    "key argument does not contain a valid AES key");
        } catch (BadPaddingException e) {
            // you'd better know about padding oracle attacks
            return null;
        } catch (GeneralSecurityException e) {
            throw new IllegalStateException(
                    "Unexpected exception during decryption", e);
        }
    }

使用方法:

    String plain = "Zaphod's just zis guy, ya knöw?";
    String encrypted = encrypt(plain, "000102030405060708090A0B0C0D0E0F");
    System.out.println(encrypted);
    String decrypted = decrypt(encrypted, "000102030405060708090A0B0C0D0E0F");
    if (decrypted != null && decrypted.equals(plain)) {
        System.out.println("Hey! " + decrypted);
    } else {
        System.out.println("Bummer!");
    }

2
要在Java 7中使用“AES/GCM/NoPadding”模式加密,请将IvParameterSpec的代码替换为'GCMParameterSpec params = new GCMParameterSpec(blockSize * Byte.SIZE, ivData);',并不要忘记处理AEADBadTagException以捕获完整性异常。Bouncy将通过简单地使用“AES/GCM/NoPadding”来工作,并会抛出BadPaddingException,返回null。 - Maarten Bodewes
1
@YuDroid 这只是一个(base 64)编码器/解码器。直到不久前,基本的API并不支持 Base64 或十六进制编码/解码,即使现在它仅包含在可选的 javax.xml.bind 包中。你可以使用 Android 中包含的 Base64 类。我选择这个类是因为我不想让代码依赖于其他库。 - Maarten Bodewes
@owlstead,您能否提供一个使用Android API中的Base64的示例?我不确定要使用哪些方法来替换DatatypeConverter方法。 - Catalin Morosan
1
对我不起作用。我正在用Base64.encode(symKeyHex.getBytes(), Base64.DEFAULT)替换这个DatatypeConverter.parseHexBinary(symKeyHex)和其他DatatypeConverter的东西,但它给我抛出了异常java.lang.IllegalArgumentException: key argument does not contain a valid AES key,我做错了什么。谢谢 - Zeeshan
1
@owlstead,您能否在某个地方发布一个示例,说明如何将DatatypeConverter.parseHexBinary(symKeyHex)和其他DataTypeConverter函数更改为Base64?您给出的提示可能对您来说很明显,但我是一个新手,在那条评论中无法理解要点。:( - Darpan
显示剩余4条评论

4
这是使用Base64 Util类而不是DatatypeConverter的版本。
public static String encrypt(final String plainMessage,
                             final String symKeyHex) {
    final byte[] symKeyData = Base64.decode(symKeyHex,Base64.DEFAULT);

    final byte[] encodedMessage = plainMessage.getBytes(Charset
            .forName("UTF-8"));
    try {
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        final int blockSize = cipher.getBlockSize();

        // create the key
        final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

        // generate random IV using block size (possibly create a method for
        // this)
        final byte[] ivData = new byte[blockSize];
        final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
        rnd.nextBytes(ivData);
        final IvParameterSpec iv = new IvParameterSpec(ivData);

        cipher.init(Cipher.ENCRYPT_MODE, symKey, iv);

        final byte[] encryptedMessage = cipher.doFinal(encodedMessage);

        // concatenate IV and encrypted message
        final byte[] ivAndEncryptedMessage = new byte[ivData.length
                + encryptedMessage.length];
        System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
        System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage,
                blockSize, encryptedMessage.length);

        final String ivAndEncryptedMessageBase64 = Base64.encodeToString(ivAndEncryptedMessage,Base64.DEFAULT);

        return ivAndEncryptedMessageBase64;
    } catch (InvalidKeyException e) {
        throw new IllegalArgumentException(
                "key argument does not contain a valid AES key");
    } catch (GeneralSecurityException e) {
        throw new IllegalStateException(
                "Unexpected exception during encryption", e);
    }
}

public static String decrypt(final String ivAndEncryptedMessageBase64,
                             final String symKeyHex) {
    final byte[] symKeyData = Base64.decode((symKeyHex),Base64.DEFAULT);

    final byte[] ivAndEncryptedMessage = Base64.decode(ivAndEncryptedMessageBase64,Base64.DEFAULT);
    try {
        final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        final int blockSize = cipher.getBlockSize();

        // create the key
        final SecretKeySpec symKey = new SecretKeySpec(symKeyData, "AES");

        // retrieve random IV from start of the received message
        final byte[] ivData = new byte[blockSize];
        System.arraycopy(ivAndEncryptedMessage, 0, ivData, 0, blockSize);
        final IvParameterSpec iv = new IvParameterSpec(ivData);

        // retrieve the encrypted message itself
        final byte[] encryptedMessage = new byte[ivAndEncryptedMessage.length
                - blockSize];
        System.arraycopy(ivAndEncryptedMessage, blockSize,
                encryptedMessage, 0, encryptedMessage.length);

        cipher.init(Cipher.DECRYPT_MODE, symKey, iv);

        final byte[] encodedMessage = cipher.doFinal(encryptedMessage);

        // concatenate IV and encrypted message
        final String message = new String(encodedMessage,
                Charset.forName("UTF-8"));

        return message;
    } catch (InvalidKeyException e) {
        throw new IllegalArgumentException(
                "key argument does not contain a valid AES key");
    } catch (BadPaddingException e) {
        // you'd better know about padding oracle attacks
        return null;
    } catch (GeneralSecurityException e) {
        throw new IllegalStateException(
                "Unexpected exception during decryption", e);
    }
}

提醒那些遇到Padding异常的人。确保您使用了正确的密钥长度。提示:看看Maarten的帖子:他的十六进制恰好为32;这不是巧合 :)


有任何想法如何将所有这些编码/解码过程翻译成Swift吗? - Starwave

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