f l a m e . o r g

organized flames

Ruby and OpenSSL

Posted on February 28, 2009

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 TypeItemDescription
RSA PublicePublic Exponent
RSAnModulus
RSA PrivatedPrivate Exponent
RSA PrivatepPrime 1
RSA PrivateqPrime 2
RSA Privatedmq1Exponent 1
RSA Privatedmp1Exponent 2
RSA PrivateiqmpCoefficient

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 TypeItemDescription
DSA Publicpub_keyPublic Key
DSAqPrime 1
DSApPrime 2
DSAgMultiplicative order modulo p is q
DSA Privatepriv_keyPrivate 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