使用PEM编码、加密的私钥原生地签名消息

14

我正在尝试使用存储在磁盘上的privateKey.pem文件中的PEM(X.509)证书来签署通过Java套接字发送的消息,但很难找到一个相关的示例。作为一个C ++程序员,我只是帮忙处理这个项目,因此当我不熟悉API时将代码组合成可运行代码变得有些困难。

不幸的是,我仅限于使用Java(1.6.0更新16)自带的方法,因此虽然我找到了一个类似的示例,使用BouncyCastlePEMReader,但它对这个特定项目没有太大帮助。

我的privateKey.pem密钥受密码保护,格式如下:

-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED DEK-Info:
DES-EDE3-CBC,63A862F284B1280B
[...]
tsjQI4H8lhOBuk+NelHu7h2+uqbBQzwkPoA8IqbPXUz+B/MAGhoTGl4AKPjfm9gu
OqEorRU2vGiSaUPgDaRhdPKK0stxMxbByUi8xQ2156d/Ipk2IPLSEZDXONrB/4O5
[...]
-----END RSA PRIVATE KEY-----

这个密钥是使用OpenSSL生成的:

openssl.exe genrsa -out private_key.pem 4096

在运行时之前,我无法将此密钥转换为DER或其他格式,任何必要的转换都需要在代码内部完成,因为该密钥需要易于替换且格式将保持为PEM。

我听到了一些混杂的东西,不是很确定,希望SO上的集体智慧能够帮助整合这些信息。



我听说需要对PEM证书进行Base64解码才能将其转换为可用的DER证书。我有一个叫做MiGBase64的Base64解码工具,但不太确定如何/何时进行此解码。


我在Java API文档中迷失了自己,试图找出15种不同类型的密钥、密钥存储、密钥生成器、证书等,但我不熟悉它们中的任何一个,无法正确识别我需要使用哪些,并如何将它们结合起来使用。


基本算法似乎很简单,这就是为什么我尝试编写一个同样简单的实现,但却让我特别沮丧:

1)从文件中读取privateKey.pem
2)使用Passphrase解密密钥,将私钥加载到XXX类中
3)使用密钥对象和Signature类来签署消息



非常感谢提供此问题的帮助,尤其是示例代码。我一直在为此问题寻找有用的示例,因为大多数“接近”的示例都是生成新密钥、使用BouncyCastle或以其他方式使用不适用于此处的不同形式的密钥/类。

这似乎是一个非常简单的问题,但它让我发疯了,是否有特别简单的答案?

2个回答

20

如果你正在使用 BouncyCastle,尝试以下方法:

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.security.KeyPair;
import java.security.Security;
import java.security.Signature;
import java.util.Arrays;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;
import org.bouncycastle.util.encoders.Hex;

public class SignatureExample {

    public static void main(String [] args) throws Exception {
        Security.addProvider(new BouncyCastleProvider());

        String message = "hello world";
        File privateKey = new File("private.pem");
        KeyPair keyPair = readKeyPair(privateKey, "password".toCharArray());

        Signature signature = Signature.getInstance("SHA256WithRSAEncryption");
        signature.initSign(keyPair.getPrivate());
        signature.update(message.getBytes());
        byte [] signatureBytes = signature.sign();
        System.out.println(new String(Hex.encode(signatureBytes)));

        Signature verifier = Signature.getInstance("SHA256WithRSAEncryption");
        verifier.initVerify(keyPair.getPublic());
        verifier.update(message.getBytes());
        if (verifier.verify(signatureBytes)) {
            System.out.println("Signature is valid");
        } else {
            System.out.println("Signature is invalid");
        }
    }

    private static KeyPair readKeyPair(File privateKey, char [] keyPassword) throws IOException {
        FileReader fileReader = new FileReader(privateKey);
        PEMReader r = new PEMReader(fileReader, new DefaultPasswordFinder(keyPassword));
        try {
            return (KeyPair) r.readObject();
        } catch (IOException ex) {
            throw new IOException("The private key could not be decrypted", ex);
        } finally {
            r.close();
            fileReader.close();
        }
    }

    private static class DefaultPasswordFinder implements PasswordFinder {

        private final char [] password;

        private DefaultPasswordFinder(char [] password) {
            this.password = password;
        }

        @Override
        public char[] getPassword() {
            return Arrays.copyOf(password, password.length);
        }
    } 
}

感谢您花费时间和精力回答我的问题。我不得不进行一些修改才能让它与我的密钥配合使用,但是您为此指明了方向。非常感谢! - KevenK
1
@KevenK 听到这个消息很好。需要进行哪些修改? - Kevin

4
OpenSSL命令生成密钥对并将其编码为PKCS#1格式。如果您不使用加密(未提供密码给该命令),则PEM只是用于PKCS#1 RSAPrivateKey的Base64编码DER。
不幸的是,Sun的JCE没有提供读取此格式密钥的公共接口。你有两个选择,
1.将密钥导入密钥库中,然后从那里读取它。Keytool不允许导入私钥。您可以找到其他工具来完成这项工作。
2.OAuth库具有处理此问题的函数。请查看此处的代码。

http://oauth.googlecode.com/svn/code/java/core/commons/src/main/java/net/oauth/signature/pem/PKCS1EncodedKeySpec.java


非常有用的信息,感谢您抽出时间来回答 :) 我选择了BouncyCastle,因为它似乎适合我的特定用途,但我很感激您的帮助。谢谢 - KevenK

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