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.
require 'openssl' # # Build a RSA public key. We only need to load two things # here in order to use the public key to use it to encrypt, # sign, or verify. # pub = OpenSSL::PKey::RSA::new pub.e = 65537 pub.n = 216457604585180710748301099018726389113 # At this point, this will work: crypted = pub.public_encrypt("test") # # Build an RSA private key. For the private key to work, we need # to load the entire key, private and public components. As we # should have access to both, this is not really a problem. # prv = OpenSSL::PKey::RSA::new prv.e = 65537 prv.d = 178210827022942698143906513631075003381 prv.n = 216457604585180710748301099018726389113 prv.p = 15294921647876231099 prv.q = 14152253249053866587 prv.dmp1 = 6806715058393856237 prv.dmq1 = 637679537428568107 prv.iqmp = 6672106206837437412 # Now we have a working private key. 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.
require 'openssl' # # Build an DSA private key. For the private key to work, we need # to load the entire key, private and public components. As we # should have access to both, this is not really a problem. # prv = OpenSSL::PKey::DSA::new prv.pub_key = ("899167044393666062859565588228279347268072456516837337" + "963353916587148226144760114643916732975837345856985656" + "3340384802383806137452386519280693373122367959").to_i prv.p = ("952649509730281181203079535805855260554748337655197352471196" + "869232197576949258404031665397657842790773780623545384978542" + "6685417827665656974405272756289291").to_i prv.q = 903197981571669745498020976355730183999507610553 prv.g = ("535694721480531756072717909769318961974692885092552247120424" + "749877864650255208980198391972633196543370921493242375015765" + "755160911031468160738717891191998").to_i prv.priv_key = 557886499717422048101097620625259920363848888840 # At this point, this will work: signature = prv.sign(OpenSSL::Digest::DSS1.new, "test") # # Build a DSA public key. We only need to load two things # here in order to use the public key to use it to encrypt, # sign, or verify. # pub = OpenSSL::PKey::DSA::new pub.pub_key = ("899167044393666062859565588228279347268072456516837337" + "963353916587148226144760114643916732975837345856985656" + "3340384802383806137452386519280693373122367959").to_i pub.p = ("952649509730281181203079535805855260554748337655197352471196" + "869232197576949258404031665397657842790773780623545384978542" + "6685417827665656974405272756289291").to_i pub.q = 903197981571669745498020976355730183999507610553 pub.g = ("535694721480531756072717909769318961974692885092552247120424" + "749877864650255208980198391972633196543370921493242375015765" + "755160911031468160738717891191998").to_i # Now we have a working private key. Verify the signature if pub.verify(OpenSSL::Digest::DSS1.new, signature, "test") puts "Signature verified." else puts "Signature verification failed." end