【问题标题】:Sign string with PEM PrivateKey使用 PEM PrivateKey 对字符串进行签名
【发布时间】:2017-07-07 20:08:08
【问题描述】:

我有一个 PEM 编码的私钥,我需要用它签署一个字符串。但是代码不断崩溃并出现异常:

java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0890ba:ASN.1 encoding routines:asn1_check_tlen:WRONG_TAG

密钥字符串:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI4P/+9mJV6RwCAggA 
MBQGCCqGSIb3DQMHBAg/ZWGXeLHgeASCAoAhExhFxfcikmIKbFP0rgAlJuj1r999 
... and so on...
hlgzM2p71FdC6NDVyyxbit/IzbimtJyhkRwOAnZ98yqtXWUEOx2v7CcUqiU8dSLA 
K0PsaxNTUeUcQV+Z7yJk/8HxfE1ya3u2CgPXCZsWWmbxQG/+awE0eEnZ 
-----END ENCRYPTED PRIVATE KEY-----

我尝试了很多变种,查看了很多答案,但结果都是一样的

编辑:在 James K Polk 的帮助下,我设法获得了私钥字节,但现在我得到了 java.security.NoSuchAlgorithmException: SecretKeyFactory PBES2 implementation not found. 修改后的代码:

private String sign(String dataString, String pkString, String privateKeyPass) throws Exception {
        pkString = pkString.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
        pkString = pkString.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
        pkString = pkString.replaceAll("\\s+","");
        byte[] privateKeyBytes = decryptPrivateKey(Base64.decode(pkString, Base64.DEFAULT), privateKeyPass);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));

        Signature instance = Signature.getInstance("SHA1withRSA");
        instance.initSign(privateKey);
        instance.update(dataString.getBytes(UTF_8));
        return Base64.encodeToString(instance.sign(), Base64.DEFAULT);
    }

public static byte[] decryptPrivateKey(byte[] key, String pass) throws Exception {
        PBEKeySpec passKeySpec = new PBEKeySpec(pass.toCharArray());

        EncryptedPrivateKeyInfo encryptedKey = new EncryptedPrivateKeyInfo(key);
        Timber.w("encryptedKey.getAlgName(): %s", encryptedKey.getAlgName());
        SecretKeyFactory keyFac = SecretKeyFactory.getInstance(encryptedKey.getAlgName());//PBES2
        SecretKey passKey = keyFac.generateSecret(passKeySpec);

        // Create PBE Cipher
        Cipher pbeCipher = Cipher.getInstance(encryptedKey.getAlgName());
        // Initialize PBE Cipher with key and parameters
        pbeCipher.init(Cipher.DECRYPT_MODE, passKey, encryptedKey.getAlgParameters());

        // Decrypt the private key
        return pbeCipher.doFinal(encryptedKey.getEncryptedData());
    }

编辑:我最终使用了 http://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art050 的课程:

PrivateKey privateKey = KeyImport.readPrivateKeyFile(pkFileName, privateKeyPass);

我将 keyString 保存到一个文件中,然后将它提供给 readPrivateKeyFile

【问题讨论】:

    标签: java android rsa


    【解决方案1】:

    您的私钥是根据 PKCS#8 加密的,因此您需要使用 EncryptedPrivateKeyInfo 类等。 This stackoverflow question 包含一个显示如何检索它的示例。我已经把它变成了一个更完整的例子:

    import javax.crypto.Cipher;
    import javax.crypto.EncryptedPrivateKeyInfo;
    import javax.crypto.SecretKey;
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.PBEKeySpec;
    import java.io.IOException;
    import java.nio.charset.StandardCharsets;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    import java.security.KeyFactory;
    import java.security.PrivateKey;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.util.Base64;
    import java.util.List;
    
    public class Main {
    
        private static byte [] pemFileToBytes(String filename) throws IOException {
            // read in PEM file, throw away the begin and end lines
            List<String> pemLines = Files.readAllLines(Paths.get(filename), StandardCharsets.US_ASCII);
            pemLines.remove(0);
            pemLines.remove(pemLines.size() - 1);
            String pem = String.join("", pemLines);
    
            // base64 decode and return the result.
    
            return Base64.getDecoder().decode(pem);
        }
    
        private static PrivateKey parsePrivateKey (String filename, char [] password) throws Exception{
            PBEKeySpec passKeySpec = new PBEKeySpec(password); //my password
    
            EncryptedPrivateKeyInfo encryptedKey = new EncryptedPrivateKeyInfo(pemFileToBytes(filename));
            SecretKeyFactory keyFac = SecretKeyFactory.getInstance(encryptedKey.getAlgName());
            SecretKey passKey = keyFac.generateSecret(passKeySpec);
    
            // Create PBE Cipher
            Cipher pbeCipher = Cipher.getInstance(encryptedKey.getAlgName());
            // Initialize PBE Cipher with key and parameters
            pbeCipher.init(Cipher.DECRYPT_MODE, passKey, encryptedKey.getAlgParameters());
    
            // Decrypt the private key
    
            byte [] encodedPrivateKey = pbeCipher.doFinal(encryptedKey.getEncryptedData());
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(encodedPrivateKey);
            KeyFactory kf = KeyFactory.getInstance("RSA");
            return kf.generatePrivate(privateKeySpec);
    
        }
    
        public static void main(String[] args) throws Exception {
            PrivateKey pk = parsePrivateKey("x.pk8", "pass".toCharArray());
        }
    }
    

    最后一行,return new String(instance.sign(), UTF_8); 没有任何意义,因为Signature.sign() 返回的字节数组在任何字符集中都不可能是有效字符串。如果您必须将签名转换为字符串,则标准方法是对其进行 base64 编码。

    【讨论】:

    • 我得到“ASN1Exception:错误的内容长度”,无论我向这个函数提供什么字节 []:整个 pem 字符串,没有标题,没有标题 base64 解码。我应该从 pem 字符串中获取什么字节 []?
    • @ildarishalin:我已经编辑了我的答案以展示一个更完整的例子。
    • 谢谢,我会将您的回答标记为已接受。但是,我仍然在“SecretKeyFactory.getInstance(encryptedKey.getAlgName())”处得到 NoSuchAlgorithmException。我尝试了一些 android 支持的算法,但没有运气。我想,我将不得不尝试使用 openssl
    • @ildarishalin:我会在 Android 上进行更多研究,我在 Java 8 SE 上进行了测试。
    • 到目前为止我得到了什么:我使用 String algorithm = "PBEwithSHA1ANDRC2"; 而不是 encryptedKey.getAlgName()(它没有设置,我是从 SecretKeyFactory android 文档中获取的)。这将我带到InvalidAlgorithmParameterException: PBE requires PBE parameters to be set.
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-06-11
    相关资源
    最近更新 更多