Skip to content

Instantly share code, notes, and snippets.

@tasuten
Last active November 24, 2015 21:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tasuten/9324404aaa0c9862affb to your computer and use it in GitHub Desktop.
Save tasuten/9324404aaa0c9862affb to your computer and use it in GitHub Desktop.
Arranged https://github.com/ius/rsatool, written by Ruby
#!/usr/bin/env ruby
# encoding : utf-8
# LICENSE: Public Domain
# all of this libraries are Ruby's standard library
# no gem required
require 'base64'
require 'openssl'
require 'optparse'
# class definition
class RSAKeyGenerator
attr_reader :p, :q, :n, :d, :e
# e is hard-coded as 65537 in OpenSSL
def initialize(p: nil, q: nil, e: 65537)
@p = p
@q = q
@e = e
_calc
end
def to_pem(type = :private)
if type == :private
private_key = Base64.encode64(to_der(:private))
return \
"-----BEGIN RSA PRIVATE KEY-----\n" \
"#{private_key}" \
"-----END RSA PRIVATE KEY-----\n"
elsif type == :public
public_key = Base64.encode64(to_der(:public))
return \
"-----BEGIN PUBLIC KEY-----\n" \
"#{public_key}" \
"-----END PUBLIC KEY-----\n"
else
fail 'key type is :private or :public'
end
end
def to_der(type = :private)
seq = nil
if type == :private
# PKCS#1 RSAPrivateKey Format
seq = OpenSSL::ASN1::Sequence.new([
OpenSSL::ASN1::Integer.new(0),
OpenSSL::ASN1::Integer.new(@n),
OpenSSL::ASN1::Integer.new(@e),
OpenSSL::ASN1::Integer.new(@d),
OpenSSL::ASN1::Integer.new(@p),
OpenSSL::ASN1::Integer.new(@q),
OpenSSL::ASN1::Integer.new(@d_mod_p_1),
OpenSSL::ASN1::Integer.new(@d_mod_q_1),
OpenSSL::ASN1::Integer.new(@q_inv)
])
elsif type == :public
# OpenSSL uses X.509 SubjectPublicKeyInfo format
# for public key
# ref: https://www.openssl.org/docs/manmaster/apps/rsa.html
values = OpenSSL::ASN1::Sequence.new([
OpenSSL::ASN1::Integer.new(@n),
OpenSSL::ASN1::Integer.new(@e)
])
seq = OpenSSL::ASN1::Sequence.new([
OpenSSL::ASN1::Sequence.new([
OpenSSL::ASN1::ObjectId.new('rsaEncryption'),
OpenSSL::ASN1::Null.new(nil)
]),
OpenSSL::ASN1::BitString.new(values.to_der)
])
else
fail 'key type is :private or :public'
end
seq.to_der
end
# under this line, all defined method is private
def _calc
@n = @p * @q
phi = (@p - 1) * (@q - 1)
fail 'e must be s.t. gcd(e, phi(n)) = 1' unless @e.gcd(phi) == 1
@d = @e.mod_inverse(phi)
@d_mod_p_1 = @d % (@p - 1)
@d_mod_q_1 = @d % (@q - 1)
@q_inv = @q.mod_inverse(@p)
end
private :_calc
end
class Integer
# return r, such like (self * r) % m == 1
def mod_inverse(m)
bn_self = OpenSSL::BN.new(self.to_s)
bn_m = OpenSSL::BN.new(m.to_s)
bn_r = OpenSSL::BN.new(bn_self).mod_inverse(bn_m)
bn_r.to_i
end
end
# entry point
# when this file is runned from command-line
if __FILE__ == $PROGRAM_NAME
# default values
args = { e: 65537, type: :private, format: :pem }
OptionParser.new do |options|
options.banner = "#{__FILE__}: Print OpenSSL RSA key from prime numbers\n"
options.banner += "Usage: #{__FILE__} -p prime1 -q prime2\n"
options.banner += " [-e exponent] [-t pubic|private] [-f pem|der]\n"
options.on('-p prime', /(?:0b|0o|0d|0x)?[1-9a-fA-F]+[0-9a-fA-F]*/,
'first prime numer') { |p| args[:p] = p.to_i(0) }
options.on('-q prime', /(?:0b|0o|0d|0x)?[1-9a-fA-F]+[0-9a-fA-F]*/,
'second prime number') { |q| args[:q] = q.to_i(0) }
options.on('-e positive_integer',
/(?:0b|0o|0d|0x)?[1-9a-fA-F]+[0-9a-fA-F]*/,
'exponent (default: 65537)') { |e| args[:e] = e.to_i(0) }
options.on('-t public|private', [:public, private],
'key type (default: private)') { |t| args[:type] = t }
options.on('-f pem|der', [:pem, :der],
'key format (default: pem)') { |f| args[:format] = f }
options.parse!(ARGV)
end
unless args.key?(:p) && args.key?(:q)
warn 'p and q are required.'
warn "see #{__FILE__} -h"
exit false
end
keys = RSAKeyGenerator.new(p: args[:p], q: args[:q], e: args[:e])
output = ''
if args[:format] == :pem
output = keys.to_pem(args[:type])
elsif args[:format] == :der
output = keys.to_der(args[:type])
end
puts output
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment