Continuing on the topic of encryption, it turns out that the work project I mentioned in my last post will require transmission of encrypted data from C# to Ruby. As this encrypted data must only be decrypt-able by the Ruby-side logic, asymmetric encryption fits the bill well. Here are some notes on working with RSA public key encryption with OpenSSL to generate the RSA key-pair; Ruby/OpenSSL to extract the public key parameters to feed the C# RSACryptoServiceProvider; C# to use the public key to encrypt an arbitrary array of bytes; and finally back to Ruby/OpenSSL to decrypt the enciphered data with the private key.
Now, I will consider that trust between the C# logic and the Ruby/OpenSSL logic is implied, so there is no need for use of X.509 certificates or what-not to distribute the public key to the C#-side. I will instead use the RSACryptoServiceProvider's FromXmlString method to initialize the provider with the key modulus and exponent. Practically speaking, I would have to obtain the key modulus and exponent (most likely using Ruby/OpenSSL), and then serialize that in an XML form that the C# class RSACryptoServiceProvider understands.
RSA Private Key Generation with OpenSSL
OK, let's start off by first using OpenSSL to generate a 512-bit RSA private key:
... with OpenSSL command-line ...
... generate RSA private key (default of 512-bits)
shino# openssl genrsa -out rsa.priv
... confirm key parameters
shino# openssl rsa -in [rsa.priv] -text -noout
Simple enough, no? And once again, yeah, yeah, yeah: a 512-bit RSA key cannot do much in the way of protecting secrets.
Cracked way back in 1999. But let's just agree to go with 512-bits for the sake of this post. BTW, don't forget that you will have to take into account any steps needed to protect this RSA private key file. I leave that exercise to you, dear reader.
Obtain Key Modulus & Exponent Parameters with Ruby/OpenSSLNext, we use Ruby/OpenSSL to read in our newly-created RSA private key and obtain Base64-encoded string values for key modulus and exponent for the RSACryptoServiceProvider's XML string (we will use those values to calculate the
public key in C#-land shortly):
# with irb/ruby/openssl
# use ruby/openssl to load private key
irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> require 'base64'
=> true
irb(main):003:0> privkey = OpenSSL::PKey::RSA.new(File.read('rsa.priv'))
# extract key exponent and Base64-encode
irb(main):004:0> exp = []
=> []
irb(main):005:0> pubkey = privkey.public_key
irb(main):006:0> pubkey.e
=> 65537
irb(main):007:0> (pubkey.e.num_bytes-1).downto(0) {|i| exp << ((pubkey.e >> (i*8)).to_i & 0xFF) }
=> 2
irb(main):008:0> exp
=> [1, 0, 1]
irb(main):009:0> tmp = []
=> []
irb(main):010:0> tmp[0] = exp.map {|b| sprintf("%02x" % b) }.join
=> "010001"
irb(main):011:0> Base64.encode64(tmp.pack('H*')).split("\n").join
=> "AQAB"
# extract key modulus and Base64-encode
irb(main):012:0> pubkey.n
=> 10859685957917547658301583853465231245284761442925449111820039202653087969032
48227098143938496267490334944907126548361037059959257154028677097618826672949
irb(main):013:0> (pubkey.n.num_bytes-1).downto(0) { |i| mod << ((pubkey.n >> (i*8)).to_i & 0xFF) }
=> 63
irb(main):014:0> mod
=> [207, 89, 10, 12, 14, 23, 85, 80, 200, 210, 18, 182, 229, 231, 180, 219, 103,
58, 118, 47, 162, 38, 14, 74, 94, 114, 192, 69, 174, 176, 175, 140, 177,
230, 202, 21, 102, 169, 66, 199, 161, 128, 61, 223, 167, 210, 168, 145, 51,
164, 94, 120, 73, 250, 100, 8, 7, 56, 185, 23, 28, 157, 59, 53]
irb(main):015:0> tmp = []
=> []
irb(main):016:0> tmp[0] = mod.map {|b| sprintf("%02x" % b) }.join
=>"cf590a0c0e175550c8d212b6e5e7b4db673a762fa2260e4a5e72c045aeb0af8cb1e6ca1566a942c7a1803ddfa7d2a89133a45e7849fa64080738b9171c9d3b35"
irb(main):017:0> Base64.encode64(tmp.pack('H*')).split("\n").join
=> "z1kKDA4XVVDI0hK25ee022c6di+iJg5KXnLARa6wr4yx5soVZqlCx6GAPd+n0qiRM6ReeEn6ZAgHOLkXHJ07NQ=="
A good point to bear in mind here is that for Ruby/OpenSSL, the key parameters are instances of OpenSSL::BN, which represent big numbers for OpenSSL for multiprecision integer arithmetics. OpenSSL::BN.num_bytes tells us how many bytes are used to represent a key parameter, and then in order to transform the big number into an array of bytes, we can pass a block that uses a bit mask to obtain the significant bits for each byte of the big number. Then, we transform the byte values in this array into 2-char hexadecimal strings, and concatenate into one, long string of hexadecimal chars. Stuff that into a temporary array, call
pack('H*') on the array, remove any line-break chars, and then we Base64-encode the values for modulus and exponent.
Encryption Using RSA Public Key with C#OK, so we have our 512-bit RSA private key, and we have extracted the key modulus and exponent values as Base64-encoded strings, which we can now pass to C#'s RSACryptoServiceProvider in order to obtain the public key:
using System.Security.Cryptography;
...
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.FromXmlString("
<RSAKeyValue>
<Modulus>
z1kKDA4XVVDI0hK25ee022c6di+iJg5KXnLARa6wr4yx5soVZqlCx6GAPd+n0qiRM6ReeEn6ZAgHOLkXHJ07NQ==
</Modulus>
<Exponent>
AQAB
</Exponent>
</RSAKeyValue>");
Note that the XML string really shouldn't have any line breaks, I only broke it up to for viewing's sake.
The next step is to encrypt some arbitrary data, which we take to be an array of bytes. Be sure to Base64-encode the result, since we will pass that string over to the Ruby/OpenSSL side.
... in C# ...
byte[] cipherbytes = rsa.Encrypt(plainbytes, false);
String ciphertext = Convert.ToBase64String(cipherbytes);
Gotta love C#, the APIs are for the most part crystal clear and user-friendly.
Decrypt Ciphertext with RSA Private Key with Ruby/OpenSSLAlright, now we just need to bring it all home, and decrypt that ciphertext string with the RSA private key, using Ruby/OpenSSL:
# with irb/ruby/openssl again
# read in private key from file
irb(main):001:0> require 'openssl'
=> true
irb(main):002:0> require 'base64'
=> true
irb(main):003:0> privkey = OpenSSL::PKey::RSA.new(File.read('rsa.priv'))
# Base64 decode the transmitted ciphertext first
irb(main):004:0> ciphertxt = Base64.decode64("RALJTqgPNRcmvZ4E09y4NODl+w/vJPRslmxi8xB9JPSrpDQ
bKjOkoeivTOqL9gT4WBpRpIlqut3jflkcUy/5Qg==")
=> "D\002\311N\250\0175\027&\275\236\004\323\334\2704\340\345\373\017\357$\364l\
226lb\363\020}$\364\253\2444\e*3\244\241\350\257L\352\213\366\004\370X\032Q\244\
211j\272\335\343~Y\034S/\371B"
# decrypt w/ private key
irb(main):005:0> plaintext = privkey.private_decrypt(ciphertxt)
=> "Ru'\035\314`\f\352"
# reconstitute as plain byte array
irb(main):006:0> plaintext.unpack('H*')[0].scan(/../).map{|b|b.hex}
=> #... results in the original array of bytes which we encrypted in C# land!
ConclusionThat wasn't so bad now, was it? We created an RSA private key with OpenSSL, used the Ruby/OpenSSL binding to manipulate the key to extract the key modulus and exponent (the public key material), and fed that into some C# code. This allowed us to encrypt an arbitrary array of bytes with the RSA public key in C#-land, and then we wrapped it up by decrypting the ciphertext with the corresponding RSA private key with Ruby/OpenSSL.
Like I mentioned in
my last post, it is the lack of clear documentation for the Ruby/OpenSSL API that can trip you up. However, hackable nature of Ruby made it fairly painless to explore and experiment, and to come up with the above example of RSA asymmetric encryption using OpenSSL, Ruby and C#.