问题
I'm using DKIM for JavaMail to sign outgoing mail with DKIM.
My private DKIM key is generated with opendkim-genkey -s default -d example.com
and looks like this:
-----BEGIN RSA PRIVATE KEY-----
ABCCXQ...[long string]...SdQaZw9
-----END RSA PRIVATE KEY-----
The DKIM for JavaMail library needs the private DKIM key in DER format as stated in their readme file:
DKIM for JavaMail needs the private key in DER format, you can transform a PEM key with openssl:
openssl pkcs8 -topk8 -nocrypt -in private.key.pem -out private.key.der -outform der
I am looking for a way to avoid having to use openssl to convert my key to DER format. Instead I would like to do the conversion in Java directly.
I have tried different suggestions (1, 2, 3) but nothing has worked so far.
DKIM for Java processes the DER file like this:
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);
So in the end what I need is the RSAPrivateKey
.
How can I easily generate this RSAPrivateKey
that DKIM for JavaMail requires from my RSA private key?
回答1:
Your reference 3 (only) is correct; as it says your problem is not just converting PEM to DER (which as @Jim says is basically just base64 to binary) but converting PEM containing openssl "traditional" or "legacy" or "PKCS#1" format key data to DER containing PKCS#8 (and specifically PKCS#8 clear/unencrypted) format key data.
The http://juliusdavies.ca/commons-ssl/pkcs8.html pointed to by Alistair's answer looks like it might be a possibility, but I didn't examine in detail. Since PKCS#8 clear (PrivateKeyInfo) for RSA is just a simple ASN.1 wrapper around the PKCS#1, the following (kinda) quick and (very) dirty code provides a minimal solution. Alter the input-reading logic (and error handling) to taste and substitute an available base64 decoder.
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);
Aside: I assume the ABCC in your posted data was redacted. Any valid and reasonable PKCS#1 (clear) RSA key must begin with bytes 0x30 0x82 x where x is from 2 to about 9; when converted to base64 this must begin with MIIC to MIIJ.
回答2:
The PEM format is just the DER bytes encoded in Base64. You can just read the PEM file as a text file, and take only the lines in between "-----BEGIN RSA PRIVATE KEY-----" and "-----END RSA PRIVATE KEY-----".
Comments are allowed in the PEM file, so you may need to skip additional lines from the beginning until you reach the "-----BEGIN RSA PRIVATE KEY-----".
Here is some code in Ruby that converts PEM to DER just by Base64-decoding the text. You can translate this into 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
Here is openssl displaying an RSA key I just created:
$ 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)
Here is openssl displaying the DER file that the Ruby code wrote:
$ 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:
...
This was the PEM (middle part omitted):
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAqo0VBduj+rvcjU/VU1bWeH9BbQW5kqR8KLERUJ53ZwTpdyJi
2x9DvPJe/PNCNaIBnJzBkLcrHMhR8ici2aOpuPZi/EDxjRcNPF1Bj+ULvYNLMEDb
2ng9HhyyQtZMllYpkuewONSsTfrsOKEm3NXFfjAclVAGUHp6JpBCTfd32VbHGmJl
...
S4SwxwUV39H2Zq+X1pSIEssSUduyGvF7jJadHe5OiacD5112bkF+Tx/YThMcMebg
8IrO8DyBIBfgAcQPq2GSY99ImacZ++OLXcXdC9NYZ3U9T2SCJdf5
-----END RSA PRIVATE KEY-----
来源:https://stackoverflow.com/questions/23709898/java-convert-dkim-private-key-from-rsa-to-der-for-javamail