今更聞けないSSL/HTTPS


SSL/HTTPSの仕組みをざっくり理解しながら、
オレオレHTTPSの稼働まで。

0.そもそもSSLって何?

概要

SSLサーバー証明書とはウェブサイトの所有者の情報、送信情報の暗号化に必要な鍵、発行者の署名データを持った電子証明書です。

SSLサーバーには主に二つの役割があります。

  • 証明書に表示されたドメインの所有者であることの証明
  • ブラウザとウェブサーバー間でのSSL暗号化通信の実現

一般的には、第三者サービスがWHOISと企業実在情報を照会して証明書を発行します。証明書を発行する人を認証局といいます。

よし、SSLサーバー証明書をつくればいいんだな。

オレオレSSLとは

"俺自身が認証局になることだ…"

社会的信用はないSSLなので運用には注意してください。

その前に、暗号化ってなんですか?

sample_cipher.rb
# encoding: utf-8

require 'OpenSSL'

def encrypt_data(data, password, salt)
    #暗号化方式を選ぶ
    cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
    cipher.encrypt
    cipher.pkcs5_keyivgen(password, salt)
    cipher.update(data) + cipher.final
end

def decode_data(data, password, salt)
    cipher = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
    cipher.decrypt
    cipher.pkcs5_keyivgen(password, salt)
    cipher.update(data) + cipher.final
end

password = "foobar" # pass_phrase
salt = "8-octets" # 8-octet string 

data = "Hello, World" # このデータを暗号化するとします

# 暗号化
encrypted_data = encrypt_data(data, password, salt)
p encrypted_data
# => "\xDEW\xFD\x8A\xB6\x83\xF1\xC1\x96\x15\x81\x02\xE3\x05\x879"

# 復号化
p decode_data( encrypted_data, password, salt )
# => "Hello, World"

パスフレーズとソルトを使って暗号化・復号化しています。

塩?

ソルトって何という疑問が浮かぶと思います。
平たくいえば、パスフレーズをランダム化するために使います。

例えばいまパスワードは"foobar"ですが、データ流出事故が起きたとします。

  • DBのpasswordの項目に"foobar"と入っている場合
    言わずもがなパスワードも流出します

  • DBのpasswordの項目に"foobar"を暗号化したもの"x83\xF1\xC1\x96\x15\x81\"が入っている場合
    "x83\xF1\xC1\x96\x15\x81\"が出るまでpass_phraseを入れ続けて一致すれば、元のpasswordが"foobar"が判明する

  • DBのpasswordの項目はその時々にソルトを使って暗号化される場合
    "x83\xF1\xC1\x96\x15\x81\"に一致するパスフレーズが見つかったとしても"foobar"は判明しない
    ソルトがわかっていたら?となるので、"foobar"をパスワードとしているユーザーが複数いたらそれぞれにソルトを変える

故にソルトは一意に生成されます
そしてソルトの長さは上の例では8-octet stringでしたが、暗号化方式によってまちまちです。

とりあえずこれだけでも秘密のトークアプリケーションがつくれますね。

RSA?

暗号化と証明書を同時につくれるナイスな方式がRSAです。
誰が暗号化するのか、誰が復号化するのかを考えると、

  1. クライアントが送る情報はクライアント側で暗号化して、
  2. 送信された情報をホスト側で受信してデコードします。

このときクライアントが勝手に暗号化するとホスト側でデコードができませんので、先にホストからクライアントに暗号化のルールを伝えます。

  1. ホストからクライアントに暗号化のルールを伝える
  2. クライアントが送る情報はクライアント側で暗号化して、
  3. 送信された情報をホスト側で受信してデコードします。

そのあたりを注目してRSAの動作を確認します。

sample_rsa.rb
# encoding: utf-8

require 'openssl'
include OpenSSL::PKey

### 1.SSL証明書を発行

# RSAを鍵長2048bitで作成します
rsa = RSA.generate(2048)

# 公開鍵を取得します
public_key = rsa.public_key.to_s

# 暗号鍵を取得します 暗号化にパスワードを入力します ここでは'password'にしてます
private_key = rsa.export(OpenSSL::Cipher::Cipher.new('aes256'),'password')

### 2.クライアント側の処理を想定
# 公開鍵をもらいます

# 発行された公開鍵でRSAを作成します
pub = RSA.new(public_key)

# 送信する情報の暗号化
enc_data = pub.public_encrypt("Personal Information")

p enc_data #=> 暗号化されたデータ よくわからない文字列

### 3.ホスト側の処理を想定
# 送信された情報を受信した!

# 暗号鍵でRSAを作成します 暗号化されているのでパスワードも必要です
private = RSA.new(private_key,'password')

# 送信された情報を復号化します
p private.private_decrypt(enc_data) #=> 'Personal Information' 復号化できました

できました。
前段が長くなりましたが、いよいよ本番です。

1.DES3という暗号化方式でRSA暗号鍵を"server.key"という名前で生成

暗号鍵のパスフレーズを設定します

$ [sudo] openssl genrsa -des3 -out server.key 1024

Enter pass phrase for server.key:
Verifying - Enter pass phrase for server.key:

# genrsa    Generation of RSA Private Key. Superceded by genpkey.
# 暗号化方式一覧
# Cipher commands (see the `enc' command for more details)
# aes-128-cbc       aes-128-ecb       aes-192-cbc       # aes-192-ecb       
# aes-256-cbc       aes-256-ecb       base64            # bf                
# bf-cbc            bf-cfb            bf-ecb            bf-ofb            
# camellia-128-cbc  camellia-128-ecb  camellia-192-cbc  camellia-192-ecb  
# camellia-256-cbc  camellia-256-ecb  cast              cast-cbc          
# cast5-cbc         cast5-cfb         cast5-ecb         cast5-ofb         
# des               des-cbc           des-cfb           des-ecb           
# des-ede           des-ede-cbc       des-ede-cfb       des-ede-ofb       
# des-ede3          des-ede3-cbc      des-ede3-cfb      des-ede3-ofb      
# des-ofb           des3              desx              rc2               
# rc2-40-cbc        rc2-64-cbc        rc2-cbc           rc2-cfb           
# rc2-ecb           rc2-ofb           rc4               rc4-40            
# seed              seed-cbc          seed-cfb          seed-ecb          
# seed-ofb          zlib    

2.RSA暗号鍵から"server.csr"という名前で証明書を発行

証明書に必要な情報をいろいろ聞かれます。

$ [sudo] openssl req -new -key server.key -out server.csr

Enter pass phrase for server.key: [暗号鍵のパスワード]

Country Name (2 letter code) [XX]:
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

3.毎回のパスワード入力を避けるため暗号鍵を復号化

$ [sudo] cp server.key server.key.org
$ [sudo] openssl rsa -in server.key.org -out server.key
Enter pass phrase for server.key.org:
writing RSA key

#  openssl rsa [-in filename] : ファイルをRSA復号
#
#  -in filename
#    入力する証明書要求のファイル名(filename)
#    デフォルトは、標準出力
#  -out filename
#    出力する証明書要求のファイル名(filename)
#    デフォルトは、標準出力

4.SSL証明書の作成

とりあえず30日の期限にします。

$ openssl x509 -req -days 30 -in server.csr -signkey server.key -out server.crt

# 形式
#   openssl req [-new] [-in filename] [-out filename] # [-key filename]
#               [-x509] [-days n]
# 機能
#   証明書の署名要求(CSR)の作成
# オプション
#   -in filename
#     入力する証明書要求のファイル名(filename)
#     デフォルトは、標準出力
#   -out filename
#     出力する証明書要求のファイル名(filename)
#     デフォルトは、標準出力
#   -signkey filename
#     入力する秘密鍵のファイル名(filename)
#   -days n
#     X.509形式の証明書の有効期限をn日とする

以上でオレオレSSL証明書が発行できました。

(5.nginxで動作させてみる)

nginxのconfに書いてみる

server {
    :
    listen 443 default ssl;
    ssl on;
    ssl_certificate     /path/to/server.crt;
    ssl_certificate_key /path/to/server.key;
    :
}

サーバーを再起動させてアクセスすると、

うまくいきました。