I recently had to do some DNSSEC-type (somewhat low-level) cryptography work, and found the seeming lack of Ruby OpenSSL documentation a big pain. I found numerous examples of how OpenSSL is commonly used with PEM-encoded keys, but precious little information on low-level key loading. To save others the trouble of having to dig up some of this, I’ve collected some short examples of how to do low-level RSA and DSA building from a lower level than most use.
This table summarizes the variables which need to be set to use an RSA public and private key.
RSA Keys
Key Type | Item | Description |
---|---|---|
RSA Public | e | Public Exponent |
RSA | n | Modulus |
RSA Private | d | Private Exponent |
RSA Private | p | Prime 1 |
RSA Private | q | Prime 2 |
RSA Private | dmq1 | Exponent 1 |
RSA Private | dmp1 | Exponent 2 |
RSA Private | iqmp | Coefficient |
Thus, in order to make a working RSA public key (so the method key.public_encrypt()
or key.public_decrypt()
work) you must set at least n
and e
. For a working private key, you would need to load all of the items. Exposing any of the items marked as “RSA Private” above will cause a key compromise.
RSA Example
In this example, a 128-bit RSA key is loaded from numerical values. In DNSSEC, the public key is stored in the DNSKEY record for the zones. Don’t use these numbers for real crypto; the short key length is used only to make the numbers short enough to fit in the screen width. For real work, 1024 is probably a reasonable minimum length for short-lived uses, and 2048 for longer-term use.
1 require 'openssl'
2
3 #
4 # Build a RSA public key. We only need to load two things
5 # here in order to use the public key to use it to encrypt,
6 # sign, or verify.
7 #
8 pub = OpenSSL::PKey::RSA::new
9 pub.e = 65537
10 pub.n = 216457604585180710748301099018726389113
11
12 # At this point, this will work:
13 crypted = pub.public_encrypt("test")
14
15 #
16 # Build an RSA private key. For the private key to work, we need
17 # to load the entire key, private and public components. As we
18 # should have access to both, this is not really a problem.
19 #
20 prv = OpenSSL::PKey::RSA::new
21 prv.e = 65537
22 prv.d = 178210827022942698143906513631075003381
23 prv.n = 216457604585180710748301099018726389113
24 prv.p = 15294921647876231099
25 prv.q = 14152253249053866587
26 prv.dmp1 = 6806715058393856237
27 prv.dmq1 = 637679537428568107
28 prv.iqmp = 6672106206837437412
29
30 # Now we have a working private key.
31 puts prv.private_decrypt(crypted) # prints "test"
DSA keys
A DSA key is more or less the same, just with different variable names. It is also split into a public and private part, and the key can be loaded from individual components just as easily.
Key Type | Item | Description |
---|---|---|
DSA Public | pub_key | Public Key |
DSA | q | Prime 1 |
DSA | p | Prime 2 |
DSA | g | Multiplicative order modulo p is q |
DSA Private | priv_key | Private Key |
DSA Example
Unfortunately, this example has some numbers which are too long to display nicely. I have used a trick to convert them from strings into integers so they will fit here. Normally you would not need to do this.
1 require 'openssl'
2
3 #
4 # Build an DSA private key. For the private key to work, we need
5 # to load the entire key, private and public components. As we
6 # should have access to both, this is not really a problem.
7 #
8 prv = OpenSSL::PKey::DSA::new
9 prv.pub_key = ("899167044393666062859565588228279347268072456516837337" +
10 "963353916587148226144760114643916732975837345856985656" +
11 "3340384802383806137452386519280693373122367959").to_i
12 prv.p = ("952649509730281181203079535805855260554748337655197352471196" +
13 "869232197576949258404031665397657842790773780623545384978542" +
14 "6685417827665656974405272756289291").to_i
15 prv.q = 903197981571669745498020976355730183999507610553
16 prv.g = ("535694721480531756072717909769318961974692885092552247120424" +
17 "749877864650255208980198391972633196543370921493242375015765" +
18 "755160911031468160738717891191998").to_i
19 prv.priv_key = 557886499717422048101097620625259920363848888840
20
21 # At this point, this will work:
22 signature = prv.sign(OpenSSL::Digest::DSS1.new, "test")
23
24 #
25 # Build a DSA public key. We only need to load two things
26 # here in order to use the public key to use it to encrypt,
27 # sign, or verify.
28 #
29 pub = OpenSSL::PKey::DSA::new
30 pub.pub_key = ("899167044393666062859565588228279347268072456516837337"
31 "963353916587148226144760114643916732975837345856985656" +
32 "3340384802383806137452386519280693373122367959").to_i
33 pub.p = ("952649509730281181203079535805855260554748337655197352471196" +
34 "869232197576949258404031665397657842790773780623545384978542" +
35 "6685417827665656974405272756289291").to_i
36 pub.q = 903197981571669745498020976355730183999507610553
37 pub.g = ("535694721480531756072717909769318961974692885092552247120424" +
38 "749877864650255208980198391972633196543370921493242375015765" +
39 "755160911031468160738717891191998").to_i
40
41 # Now we have a working private key. Verify the signature
42 if pub.verify(OpenSSL::Digest::DSS1.new, signature, "test")
43 puts "Signature verified."
44 else
45 puts "Signature verification failed."
46 end