Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 236 lines (194 sloc) 6.31 KB
#!/usr/bin/env ruby
# dh -- Diffie-Hellman key exchange
require 'base64'
require 'openssl'
require 'optparse'
def bn_output(bn)
return nil if !bn
hex = bn.to_s(16)
raw = [hex].pack("H*")
b64 = Base64.strict_encode64(raw)
return b64
end
def bn_input(str)
return nil if !str
str = File.read($1) if str =~ /^@(.+)/
str.strip!
return nil if str == "" || str == "nil"
b64 = str
raw = Base64.decode64(str)
hex = raw.unpack("H*")[0]
OpenSSL::BN.new(hex, 16)
end
def buf_to_hex(buf)
buf.unpack("H*")[0]
end
def color(str, fmt)
if STDOUT.tty?
"\e[#{fmt}m#{str}\e[m"
else
str
end
end
def ask(str)
if STDIN.tty?
STDOUT.print "#{str} "
STDOUT.flush
end
STDIN.gets
end
def check_key_size!(dh, key, descr)
if (dh.p.num_bits - key.num_bits).abs > 16
abort "key size mismatch: #{descr} is #{key.num_bits} bits," \
+ " should be #{dh.p.num_bits} bits"
end
end
builtin_params = {}
builtin_params[256] = <<EOF
-----BEGIN DH PARAMETERS-----
MCYCIQDMkOHJmT8qYX17pA2nPh+QbgJ0q4IHGAcv+SRvkbVA6wIBAg==
-----END DH PARAMETERS-----
EOF
builtin_params[512] = <<EOF
-----BEGIN DH PARAMETERS-----
MEYCQQCKWqXw186zTL4nVOVj5CJRXvN0I3WDowMk8OYUsR6Dy43C7KnFS7MG59lr
Wc5GFJTTVawkEcgecLh30CkaEVhLAgEC
-----END DH PARAMETERS-----
EOF
builtin_params[1024] = <<EOF
-----BEGIN DH PARAMETERS-----
MIGHAoGBAPcVYFqTRcfOBpBrtS41hSPtj/0OfxNSxFjUWnB81Vw3SKLbCYuvpKdv
GWKBb9ECvlKgfdIhs44tsA9nL8uXZOVqAQhCbldaL9gafPGCOFcU+aP/2j1eOUJO
/tPOCUxp9jP1mbIQKRZ0wMXG1n6f+zrwsFP+Cwt6bSEhHwIsfEC7AgEC
-----END DH PARAMETERS-----
EOF
builtin_params[2048] = <<EOF
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEAtp9N1rD42nLs1Jsj72RTYmY2FL9S3844VgIpwE+xOfiA2PGNNC2W
mpH4pfBlPEtBR20wpZe6pIGOGeQByImwedYqvFG7azKVyhA+cwrKa63O6Mz6rSzH
pF8uk6lBLvbgvVQeSAImavnc9bsJ1k3+8/CteCiindUu6Njw7KHAjYq8S1vQSfDL
U0BQnV5mdbI7WvJ/rkDzLTa9DDEvHk+W3bMDCTNWJf4I10Vlz3UM+fbozpxAkfnm
yb4R9IjvvZywjDmgQX8pmY7sOhAE+gb7/5I27NGlVQrSt9xEpgxethmGlMOTR5fu
5/rkR/oeqjqxLzy/ebLXL+t2G9b9GXwJSwIBAg==
-----END DH PARAMETERS-----
EOF
builtin_params[4096] = <<EOF
-----BEGIN DH PARAMETERS-----
MIICCAKCAgEAokXmNXJPJxbeQVNoBlZY66ekZnsd1CeGd1BFfKpKFmyJT4TfkDm/
L0CDmHOU+UsK2+HvVNbY4K2u+oWGBLuIHTGbLZ4nxhY4hE6iquz+9wuJ1vOL34Kd
dN7KqgiEbaATQPWlYfF604MO6sypEpb0tWS7HGSEhpjdOVlyQZpCfK+Wsd08Y7be
8V9mCNqh8DwwSEMSWTI9bL0/pgLJ4GnbyfT9Hh14xeFkavdmjyR7lhg52t6P1L7Q
bSjT+Exh0vOhCUGnZDAfzyWUPOtVpTL2DX0CzSu6DfDxfgl9/Bo6u/yOqmE5jpLB
+9RA7EfiU31E/O9NLxWlzX6TXpQpl7DdDPShDCrproAV6TuLnra3uShTL8pzzfyy
Yw9NOtp5QdZVHSCd0CU3g5v+D5X71BVrs+z+2iljSgEB84pI58vzZchsFhQyfvjC
/KC5/NVDQnp6z+vbbuzDB96E+5/f3XkPZkWPtXY50Qlp36hQrnjeMG8W4zvnmLtY
dQbi34ZoeZlrNsbeW+pBdZp2VYg0Pexf0VR54gJAPIU75b0/vCPff6wWqlIaVsXR
sdt2cffTKR8EgpVAuSXh9DUYyNOBDAHo3DYQ4Swi1a8SOtNLE9YLDKxFdv946H9F
knd6YRUfA6+2izlFIkMZHXwubpjk1ME0hG+XkFwoijza/HEVIzMpfEMCAQI=
-----END DH PARAMETERS-----
EOF
params = builtin_params[2048]
my_private = nil
other_public = nil
ask_my_private = false
ask_other_public = true
secret_file = nil
show_private = false
OptionParser.new do |opts|
opts.banner = "Usage: dh [options]"
opts.separator("")
opts.on("-y", "--their-pubkey KEY", String, "Other party's public key") do |s|
other_public = bn_input(s)
ask_other_public = false
end
opts.on("-o", "--write-secret FILE", String, "Write shared secret to file") do |s|
secret_file = s
end
opts.separator("")
opts.separator("Security level:")
opts.separator("")
opts.on("-b", "--dh-bits BITS", Integer, "Size of built-in DH parameters") do |i|
if builtin_params[i]
params = builtin_params[i]
else
avail = builtin_params.keys.sort.map(&:to_s)
avail << "and #{avail.pop}"
avail = avail.join(", ")
abort "I only have built-in parameters of #{avail} bits."
end
end
opts.on("-P", "--dh-params FILE", String, "Read DH parameters from file") do |s|
params = File.read(s)
end
opts.separator("")
opts.separator("Advanced options:")
opts.separator("")
opts.on("-R", "--resumable", "Display our private key") do |t|
show_private = t
end
opts.on("-r", "--resume", "Ask for private key") do |t|
ask_my_private = true
end
opts.separator("")
opts.on("-g", "--generate-privkey", "Display private key and exit") do
show_private = true
ask_other_public = false
end
opts.on("-k", "--resume-with-privkey KEY", String, "Reuse old private key") do |s|
my_private = bn_input(s)
if my_private.nil?
ask_my_private = true
end
end
opts.separator("")
opts.separator("Remember: It is insecure to use the same private key multiple times.")
end.parse!
if !show_private and !other_public and !ask_other_public
abort "error: specifying '-y nil' without '-R' does nothing useful"
end
dh = OpenSSL::PKey::DH.new(params)
# if given our old private key, verify it and resume
if ask_my_private
my_private = bn_input(ask("my private key?")) or exit
end
if my_private
check_key_size!(dh, my_private, "my private key")
dh.priv_key = my_private
end
# if given other party's public key, verify it early
if other_public
check_key_size!(dh, other_public, "their public key")
end
if dh.p.num_bits < 1536
puts "\e[1;33mwarning:\e[m \e[1mDH parameters of #{dh.p.num_bits} bits" \
+ " are very weak\e[m"
end
# generate our private key if not given; extract our public key
dh.generate_key!
if show_private
puts "my private key: " + color(bn_output(dh.priv_key), "31")
end
puts "my public key: " + color(bn_output(dh.pub_key), "32")
# if not given other party's public key, ask for it
# (conditions reversed, yes)
if !other_public
if !ask_other_public
exit
end
other_public = bn_input(ask("their public key?")) or exit
check_key_size!(dh, other_public, "their public key")
end
# compute shared secret
secret = dh.compute_key(other_public)
length = "#{secret.length * 8} bits"
if secret_file
File.open(secret_file, "wb") do |f|
f.write(secret)
end
puts "shared secret (#{length}) written to \"#{secret_file}\""
else
puts "shared secret (#{length}): " + color(Base64.strict_encode64(secret), "36")
end
secret_hash = OpenSSL::Digest::SHA1.digest(secret)
puts "hash of secret: " + color(buf_to_hex(secret_hash), "33")
# vim: ts=4:sw=4:et