RSA公開鍵とRubyで暗号化し秘密鍵とElixirで復号化する

RSA秘密鍵、公開鍵のペアを用いた暗号化と復号化について調べた。 特にRubyで暗号化しElixirで復号化するような、言語を跨いだ処理でも問題なく行えることを確認した。

手順

RSA秘密鍵、公開鍵

RSA秘密鍵の生成

openssl のサブコマンド genrsa を使うとRSA鍵が作れる。引数によってビット数なども変えられるが今回はデフォルトのまま生成している。

$ openssl genrsa > private.pem
Generating RSA private key, 2048 bit long modulus
.............................................................................................................................................................................................................................................+++
...............+++
e is 65537 (0x10001)
$ ls
private.pem

RSA公開鍵の生成

RSA秘密鍵があれば、対になる公開鍵を生成できる。openssl のサブコマンド rsa を使う。

$ openssl rsa -in private.pem -pubout -out public.pem
writing RSA key
$ ls
private.pem public.pem

Ruby

$ ruby --version
ruby 2.7.0dev (2019-06-04 trunk 14b5ccf91b) [x86_64-darwin18]

RSA公開鍵を使ってRubyで暗号化

OpenSSL::PKey::RSA.newpublic_encrypt を利用して、RSA公開鍵を使った暗号化が行える。

public_encrypt の第二引数にあたるパディングのデフォルト値は互換性のためにセキュアでないものに設定されている。 良くわからない(=特に制約がない)場合は OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING を設定することが望ましいとドキュメントに書いてあったので従う。

以下のコードを encrypt.rb として保存する。標準入力から文字を受けとって暗号化、Base64 化、結果を標準出力から出力するコードだ。

#!/usr/bin/env ruby

require "openssl"
require "base64"

string = ARGF.read
public_key_file = "public.pem"


public_key = OpenSSL::PKey::RSA.new(File.read(public_key_file))
puts Base64.encode64(public_key.public_encrypt(string, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING))

以下のように利用する。

$ echo "hellllllllllllllloooooooooooooo" | ruby encrypt.rb > encrypted.txt

$ cat encrypted.txt
xhxN+YQslh6hA53VoWWtcI73W7+I8pgty6NeM/2Sa+o9/AaQk/pQdnFkt39l
9twKi5BRIVWrzkul5Yb1XcGWpxNQ5/lNNPcdD161f/39ETpJOcc8WHYofZAD
YsxxxlW0bemJBqxRF7QB/fQoe8BhFF5lIlNsQOya7sqWfB8dHtBETgSeKMmd
FtSEkdVznkynhJJ3CKQeSfy08ALcOhVBTcpWJ237e3Ihdnv2hb22wxaPvK2X
UEC5yrh1NHnPsxZ+nu4lhILfOV/3HTUt3aheeJ4Tmb4iZzr6847ENTpf1dPY
Z5p8TT8GyVV1fMqRGqYA3DkhSWtFUfYrS4AD2fDHaQ==

RSA秘密鍵を使ってRubyで復号化

OpenSSL::PKey::RSA.newprivate_decrypt を利用して、RSA 暗号鍵を使った復号化が行える。

private_decrypt の第二引数は public_decrypt で設定したものと同じにしなければ復号化できない。

以下のコードを decrypt.rb として保存する。標準入力から Base64 化した暗号文字を受けとって、Base64 化を解き、復号化し、結果を標準出力から出力するコードだ。

#!/usr/bin/env ruby

require "openssl"
require "base64"

encrypted_string = ARGF.read
private_key_file = "private.pem"

private_key = OpenSSL::PKey::RSA.new(File.read(private_key_file))
puts private_key.private_decrypt(Base64.decode64(encrypted_string), OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)

以下のように利用する。復号もうまく機能しているようだ。

$ cat encrypted.txt | ruby decrypt.rb
hellllllllllllllloooooooooooooo

Elixir

$ elixir --version
Erlang/OTP 22 [erts-10.4] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Elixir 1.9.0-rc.0 (aad7aa4) (compiled with Erlang/OTP 20)

RSA秘密鍵を使ってElixirで復号化

暗号化したものは当然別のプログラミング言語を使っても復号化可能だ。Elixir で復号化してみる。 鍵を扱う場合は Erlang の public_key というモジュールを使うとよい。

Ruby の場合と異なり pemdecode して pem_entry を取り出し、次に pem_entrydecode して key を取り出すという 2 手間必要であることに気をつければ、他はそれほど差異はない。

以下のコードを decrypt.exs として保存する。標準入力から Base64 化した暗号文字を受けとって、Base64 化を解き、復号化し、結果を標準出力から出力するコードだ。

#!/usr/bin/env elixir

encrypted_string = IO.read(:stdio, :all)
private_key_file = "private.pem"

[rsa_entry] = :public_key.pem_decode(File.read!(private_key_file))
private_key = :public_key.pem_entry_decode(rsa_entry)
IO.puts :public_key.decrypt_private(Base.decode64!(encrypted_string, ignore: :whitespace), private_key, rsa_pad: :rsa_pkcs1_oaep_padding)

以下のように利用する。Elixir による復号もうまく機能しているようだ。

$ cat encrypted.txt | elixir decrypt.exs
hellllllllllllllloooooooooooooo

RSA公開鍵を使ってElixirで暗号化

以下のコードを encrypt.exs として保存する。標準入力から文字を受けとって暗号化、Base64 化、結果を標準出力から出力するコードだ。

#!/usr/bin/env elixir

string = IO.read(:stdio, :all)
public_key_file = "public.pem"

[rsa_entry] = :public_key.pem_decode(File.read!(public_key_file))
public_key = :public_key.pem_entry_decode(rsa_entry)
IO.puts Base.encode64!(:public_key.encrypt_public(string, public_key, rsa_pad: :rsa_pkcs1_oaep_padding))

以下のように利用する。Elixir の Base64 化はデフォルトでは改行を付与しない模様だ。しかし問題なく復号できた。

$ echo "hellllllllllllllloooooooooooooo" | elixir encrypt.exs > encrypted2.txt

$ cat encrypted2.txt
FCf12BTnonV5R8haK08E5zTd1ypsLkKdetjNtpw5xBJ7Jb1F3hJo1TWTKkiOHTZXKCx+LMOjF0BjjvsiK2irpQv8fr68HwHuEklGCxFlM3YuaCUo2zzfZFCSubpTNggf5HHp2CJurlTa4j5BdLqJb2geg0qGCnVWhfxvNH8orbTlXTMPV8+JuvthhL36fTKwURp/F+UPmyOoipJAaCSMqkbLloGJ39wj0ENZjLNf2hDlF2gvnICApcLj3NsqxhfSw7ieN+DWzrxIiZ38w1M4+GK2YQgp+/Or0sbx7gIcb6vaF7EkRB6SxTMh88Bu6FH38Un+Vyu89Qv/8qUmIa/Mug==

$ cat encrypted2.txt | ruby decrypt.rb
hellllllllllllllloooooooooooooo

まとめ

どの組み合わせでもRSA秘密鍵、公開鍵を用いた暗号化復号化が行えるようになった。

$ echo "1234567" | ruby encrypt.rb | ruby decrypt.rb
1234567
$ echo "1234567" | ruby encrypt.rb | elixir decrypt.exs
1234567
$ echo "1234567" | elixir encrypt.exs | ruby decrypt.rb
1234567
$ echo "1234567" | elixir encrypt.exs | elixir decrypt.exs
1234567