それマグで!

知識はカップより、マグでゆっくり頂きます。 takuya_1stのブログ

習慣に早くから配慮した者は、 おそらく人生の実りも大きい。

openssl の暗号化と同等のrubyでの処理

openssl で暗号化すると便利なんだけど

暗号化をおこなったデータをそのままプログラムを経由して読みたいよね。やっぱり。

openssl で暗号化 → ruby で復号化

openssl enc -e  -aes-256-cbc -salt -in test.json -out enc.json -pass password:my_pass

これで暗号化したファイルを、rubyでデコードしてみる

      passphrase = "my_pass"
      data = open("enc.json", "r").read
      data = data.force_encoding("ASCII-8BIT")
      salt = data[8,8]
      data = data[16, data.size]
      cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
      cipher.decrypt
      cipher.pkcs5_keyivgen(passphrase, salt, 1 )
      data = cipher.update(data) + cipher.final
      open("out.json", "w"){|f| f.write data }

ruby で暗号化 → openssl で復号化

ruby で暗号化したものを openssl で復号化する。

      passphrase = "秘密秘密"
      data = open("plain.json", "r").read
      cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
      salt = OpenSSL::Random.random_bytes(8)
      cipher.encrypt
      cipher.pkcs5_keyivgen(passphrase, salt, 1 )
      data = cipher.update(data) + cipher.final
      ## salted
      data = "Salted__" + salt + data
      open("enc.json", "w"){|f| f.write data }

openssl で復号化

openssl enc -d  -aes-256-cbc -salt -in enc.json -out plain.json -pass password:秘密秘密

base64 や、pbkdf2 を用いる場合

上記の記述はずいぶんと古い記述なので、新しく調べ直した。

OpenSSLでbase64 ファイルで、salt を使う場合

## 暗号化
openssl enc -e -pbkdf2 -aes256 -salt -base64 -in my.txt -out my.enc -pass password:my_passphrase
## 復号化
openssl enc -e -pbkdf2 -aes256 -salt -base64 -in my.enc -out out.txt -pass password:my_passphrase

PBKDF2 を用いて、salt を iter 回数分だけ繰り返している。iter回数はデフォルトで10000 である。iter回数は明示したほうが総当たり強度がある。

## 暗号化 iter 明示
openssl enc -e -pbkdf2 -iter 110801-aes256 -salt -base64 -in my.txt -out my.enc -pass password:my_passphrase
## 復号化 iter 明示
openssl enc -e -pbkdf2 -iter 110801 -aes256 -salt -base64 -in my.enc -out out.txt -pass password:my_passphrase

コレと同等の処理をruby で行いたい。

復号化

  data = Base64.decode64 File.read(encrypted_file, mode: 'rb')
  raise 'no salt' unless data.start_with?("Salted__")

  # ソルトの取り出し
  salt = data[8, 8]  # ヘッダー後続8バイト
  encrypted_data = data[16..-1] # 残り全部が暗号化文
  cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
  cipher.decrypt
  key_iv = OpenSSL::PKCS5.pbkdf2_hmac(passphrase, salt, iter, cipher.key_len + cipher.iv_len, 'sha256')
  cipher.key = key_iv[0, cipher.key_len]
  cipher.iv =  key_iv[cipher.key_len, cipher.iv_len]
  # 復号化
  decrypted_data = cipher.update(encrypted_data) + cipher.final
  File.open(output_file, 'wb') {|f|f.write(decrypted_data)}

## 実行例
##  openssl enc -e -pbkdf2 -iter 110801 -aes256 -salt -base64 -in my.enc -out out.txt -pass password:my_passphrase 相当
passphrase = "my-long-pass-phrase"
iterations = 110801
decrypt_file('my.enc, 'my.out', password, iterations)

暗号化

def encrypt_file(input_file, encrypted_file, passphrase, iter)
  data = File.open(input_file, "r").read
  cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
  cipher.encrypt

  salt = OpenSSL::Random.random_bytes(8 )
  key_iv = OpenSSL::PKCS5.pbkdf2_hmac(passphrase, salt, iter, cipher.key_len + cipher.iv_len, 'sha256')
  cipher.key = key_iv[0, cipher.key_len]
  cipher.iv = key_iv[cipher.key_len, cipher.iv_len]
  encrypted_data = "Salted__" + salt + cipher.update(data) + cipher.final
  File.open(encrypted_file, 'wb') { |f| f.write Base64.encode64(encrypted_data)}
end

# 実行例
# openssl相当 
## openssl enc -e -pbkdf2 -iter 110801 -aes256 -salt -base64 \
#   -in my.enc -out out.txt -pass password:my_passphrase 
passphrase = "my-long-pass-phrase"
iterations = 110801
encrypt_file(input_file, encrypted_file, password, iterations)
puts "File encrypted to #{encrypted_file}"

これらをまとめて実行しつつ、openssl コマンドでも扱えるか試してみる。

def main
  require 'openssl'
  require 'base64'
  require 'pry'

  # 入力ファイル名と出力ファイル名
  input_file = 'my.txt'
  encrypted_file = 'my.enc'
  decrypted_file = 'my.out'
  password = 'your_password_here'  # パスワードをここに設定
  iterations = 110801
  # 暗号化
  encrypt_file(input_file, encrypted_file, password, iterations)
  puts "File encrypted to #{encrypted_file}"

  # 復号化
  decrypt_file(encrypted_file, decrypted_file, password, iterations)
  puts "File decrypted to #{decrypted_file}"

  raise "failed ruby encrypt / decrypt " unless open(input_file).read == open(decrypted_file).read

  ## 互換性チェック
  openssl_out = 'my.openssl.enc'
  # ruby encrypt_file -> openssl decrypt
  result = `openssl enc -d -aes-256-cbc -base64 -pbkdf2 -iter #{iterations} -salt -in #{encrypted_file} -k #{password}`
  raise "failed compatible check: ruby encrypt -> openssl decrypt failed." unless open(input_file).read == result
  # openssl encrypt -> ruby decrypt_file()
  `openssl enc -e -aes-256-cbc -base64 -pbkdf2 -iter #{iterations} -salt -in #{input_file} -out #{openssl_out} -k #{password}`
  decrypt_file(openssl_out, decrypted_file, password, iterations)
  raise "openssl encrypt -> ruby decrypt failed. " unless open(decrypted_file).read == open(input_file).read

end

main

コレで、問題なく、Openssl と同様の処理が行えることがわかった。

2024-09-28

ruby ソースコードで openssl コマンドと同等の処理を行ってみた。

細かいところが割とめんどくさい。pkcs5_keyivgen だとうまくいかない。いろいろと新しくなってるんですね。