Java:将 DKIM 私钥从 RSA 转换为 DER for JavaMail

Posted

技术标签:

【中文标题】Java:将 DKIM 私钥从 RSA 转换为 DER for JavaMail【英文标题】:Java: Convert DKIM private key from RSA to DER for JavaMail 【发布时间】:2014-05-17 09:51:05 【问题描述】:

我正在使用 DKIM for JavaMail 通过 DKIM 签署外发邮件。 我的私人 DKIM 密钥是使用 opendkim-genkey -s default -d example.com 生成的,如下所示:

-----BEGIN RSA PRIVATE KEY-----
ABCCXQ...[long string]...SdQaZw9
-----END RSA PRIVATE KEY-----

JavaMail 库的 DKIM 需要 DER 格式的私有 DKIM 密钥,如其自述文件中所述:

DKIM for JavaMail 需要 DER 格式的私钥,你可以 转换 PEM 密钥 使用openssl:

openssl pkcs8 -topk8 -nocrypt -in private.key.pem -out private.key.der -outform der

我正在寻找一种方法来避免使用 openssl 将我的密钥转换为 DER 格式。相反,我想直接在 Java 中进行转换。 我尝试了不同的建议(1、2、3),但到目前为止没有任何效果。 DKIM for Java 像这样处理 DER 文件:

    File privKeyFile = new File(privkeyFilename);

    // read private key DER file
    DataInputStream dis = new DataInputStream(new FileInputStream(privKeyFile));
    byte[] privKeyBytes = new byte[(int) privKeyFile.length()];
    dis.read(privKeyBytes);
    dis.close();

    KeyFactory keyFactory = KeyFactory.getInstance("RSA");

    // decode private key
    PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(privKeyBytes);
    RSAPrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privSpec);

所以最后我需要的是RSAPrivateKey

如何从我的 RSA 私钥轻松生成 DKIM for JavaMail 所需的 RSAPrivateKey

【问题讨论】:

【参考方案1】:

您的参考文献 3(仅)是正确的;正如它所说,您的问题不仅仅是转换 PEM 到 DER(正如@Jim 所说,基本上只是 base64 到二进制)但是转换 包含 openssl“传统”或“遗留”或“PKCS#1”格式密钥数据的 PEM DER 包含 PKCS#8(特别是 PKCS#8 明文/未加密)格式的密钥数据。

Alistair 的回答所指向的http://juliusdavies.ca/commons-ssl/pkcs8.html 看起来这可能是一种可能性,但我没有详细检查。由于 PKCS#8 clear (PrivateKeyInfo) for RSA 只是 PKCS#1 的简单 ASN.1 包装器, 以下(有点)快速和(非常)脏的代码提供了一个最小的解决方案。 更改输入读取逻辑(和错误处理)以尝试和替换可用的 base64 解码器。

    BufferedReader br = new BufferedReader (new FileReader (oldpem_file));
    StringBuilder b64 = null;
    String line;
    while( (line = br.readLine()) != null )
        if( line.equals("-----BEGIN RSA PRIVATE KEY-----") )
            b64 = new StringBuilder ();
        else if( line.equals("-----END RSA PRIVATE KEY-----" ) )
            break;
        else if( b64 != null ) b64.append(line);
    br.close();
    if( b64 == null || line == null ) 
        throw new Exception ("didn't find RSA PRIVATE KEY block in input");

    // b64 now contains the base64 "body" of the PEM-PKCS#1 file
    byte[] oldder = Base64.decode (b64.toString().toCharArray());

    // concatenate the mostly-fixed prefix plus the PKCS#1 data 
    final byte[] prefix = 0x30,(byte)0x82,0,0, 2,1,0, // SEQUENCE(lenTBD) and version INTEGER 
            0x30,0x0d, 6,9,0x2a,(byte)0x86,0x48,(byte)0x86,(byte)0xf7,0x0d,1,1,1, 5,0, // AlgID for rsaEncryption,NULL
            4,(byte)0x82,0,0 ; // OCTETSTRING(lenTBD) 
    byte[] newder = new byte [prefix.length + oldder.length];
    System.arraycopy (prefix,0, newder,0, prefix.length);
    System.arraycopy (oldder,0, newder,prefix.length, oldder.length);
    // and patch the (variable) lengths to be correct
    int len = oldder.length, loc = prefix.length-2; 
    newder[loc] = (byte)(len>>8); newder[loc+1] = (byte)len;
    len = newder.length-4; loc = 2;
    newder[loc] = (byte)(len>>8); newder[loc+1] = (byte)len;

    FileOutputStream fo = new FileOutputStream (newder_file);
    fo.write (newder); fo.close();
    System.out.println ("converted length " + newder.length);

旁白:我假设您发布的数据中的 ABCC 已被编辑。任何有效且合理的 PKCS#1(清除)RSA 密钥必须以字节 0x30 0x82 x 开头,其中 x 介于 2 到大约 9 之间; 当转换为 base64 时,它必须以 MIIC 开头到 MIIJ。

【讨论】:

我没有看参考文献就回答得太快了。我没有考虑 BEGIN PRIVATE KEY 和 BEGIN RSA PRIVATE KEY 之间的区别。【参考方案2】:

PEM 格式只是以 Base64 编码的 DER 字节。您可以将 PEM 文件作为文本文件读取,并且只使用“-----BEGIN RSA PRIVATE KEY-----”和“-----END RSA PRIVATE KEY----”之间的行-”。

PEM 文件中允许注释,因此您可能需要从开头跳过其他行,直到到达“-----BEGIN RSA PRIVATE KEY-----”。

这是 Ruby 中的一些代码,仅通过 Base64 解码文本即可将 PEM 转换为 DER。您可以将其翻译成 Java:

require 'base64'

# Read the PEM as text
pem = File.read('key.pem')

# Break into individual lines
all_lines = pem.split("\n")

# Discard the first and last lines
b64_lines = all_lines[1..-2]

# Write the output file
File.open('key.der', 'w') do |f|
    # For each line
    b64_lines.each do |line|
        # Write the Base64-decoded bytes
        f.write Base64.decode64(line)
    end
end

这里是显示我刚刚创建的 RSA 密钥的 openssl:

$ openssl rsa -text -in key.pem
Private-Key: (2048 bit)
modulus:
    00:aa:8d:15:05:db:a3:fa:bb:dc:8d:4f:d5:53:56:
    d6:78:7f:41:6d:05:b9:92:a4:7c:28:b1:11:50:9e:
    77:67:04:e9:77:22:62:db:1f:43:bc:f2:5e:fc:f3:
    42:35:a2:01:9c:9c:c1:90:b7:2b:1c:c8:51:f2:27:
... (omitted)

这里是显示 Ruby 代码编写的 DER 文件的 openssl:

$ openssl rsa -text -inform DER -in key.der
Private-Key: (2048 bit)
modulus:
    00:aa:8d:15:05:db:a3:fa:bb:dc:8d:4f:d5:53:56:
    d6:78:7f:41:6d:05:b9:92:a4:7c:28:b1:11:50:9e:
    77:67:04:e9:77:22:62:db:1f:43:bc:f2:5e:fc:f3:
    42:35:a2:01:9c:9c:c1:90:b7:2b:1c:c8:51:f2:27:
...

这是 PEM(中间部分省略):

-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqo0VBduj+rvcjU/VU1bWeH9BbQW5kqR8KLERUJ53ZwTpdyJi
2x9DvPJe/PNCNaIBnJzBkLcrHMhR8ici2aOpuPZi/EDxjRcNPF1Bj+ULvYNLMEDb
2ng9HhyyQtZMllYpkuewONSsTfrsOKEm3NXFfjAclVAGUHp6JpBCTfd32VbHGmJl
...
S4SwxwUV39H2Zq+X1pSIEssSUduyGvF7jJadHe5OiacD5112bkF+Tx/YThMcMebg
8IrO8DyBIBfgAcQPq2GSY99ImacZ++OLXcXdC9NYZ3U9T2SCJdf5
-----END RSA PRIVATE KEY-----

【讨论】:

以上是关于Java:将 DKIM 私钥从 RSA 转换为 DER for JavaMail的主要内容,如果未能解决你的问题,请参考以下文章

如何将 char 数组转换为结构?

如何在 Java 中将 OpenSSH 私钥转换为 RSA 私钥?

如何将私钥转换为 RSA 私钥?

将 DKIM 与 SMTP TELNET 连接一起使用

将 JSON 公钥/私钥对转换为 rsa.PrivateKey 和 rsa.PublicKey

pkcs1与pkcs8格式RSA私钥互相转换