java - 使用 PEM 私钥签名字符串

标签 java android rsa

我有一个 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 的类(class):

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

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

最佳答案

您的私钥已根据 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 编码。

关于java - 使用 PEM 私钥签名字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44921731/

相关文章:

java - .java.security.NoSuchAlgorithmException : algorithm RSA/PKCS1 is not available from provider Cryptix

java - 将 json 对象消费到一个以 json 数组作为哈希集的类中

java - 在 Java 中声明类时顺序重要吗?

java - 当您必须替换许多 fragment 时,保存 fragment 状态的最佳实践方法是什么?

java - Java 中的 JSON 解析和数据操作

android - 应用程序在 FirebaseAuth.DefaultInstance 之后崩溃

Android makefile 链接两次

android - 如何读取 Assets 文件夹中的 .pdf 文件

java - 在java中使用私钥身份验证来保护FTP

rsa - 我可以在 RSA 中交换公钥和私钥吗