Tiger Blog

自作 Socks Tunnel の暗号化について

前回の Firewall を貫くツールを自作してみた の続きです。今回は暗号化の部分を補完します。

基本的は SSL/TLS のデータ送受信の段階と同じ共通鍵暗号を使えばいいです。
今回はとりあえず一番普通の AES-256-CBC で暗号化しようと思ういます。(アルゴリズムは簡単に変えられます)

Ruby でやるなら OpenSSL::Cipher を使えば簡単に実装できます。

暗号化と復号化用のクラスはこんな感じです。基本的は Ruby のドキュメント通りです。

coder.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
require "openssl"

CIPHER = "AES-256-CBC"
SALT = "V\x11\x97\xA6r\xEF[\xFE"
PASSWORD = "mypassword"

class Coder
  def initialize
    cipher = OpenSSL::Cipher.new(CIPHER)
    key_iv = OpenSSL::PKCS5.pbkdf2_hmac_sha1(PASSWORD, SALT, 2000, cipher.key_len + cipher.iv_len)
    @key = key_iv[0, cipher.key_len]
    @iv = key_iv[cipher.key_len, cipher.iv_len]

    @encoder = OpenSSL::Cipher.new(CIPHER)
    @encoder.encrypt
    @encoder.key = @key

    @decoder = OpenSSL::Cipher.new(CIPHER)
    @decoder.decrypt
    @decoder.key = @key
  end

  def encode(data)
    @encoder.iv = @iv
    @encoder.update(data) + @encoder.final
  end

  def decode(data)
    @decoder.iv = @iv
    @decoder.update(data) + @decoder.final
  end
end

キーと IV を手元で作っても構わないが、ドキュメント通りに PBKDF2 を使って、パスワードとソルトから生成したほうが正しい道と思います。
もちろんパスワードとソルトをソースコードに書くのも良くないですけど、ここは一旦置いといて、とりあえず暗号化と復号化ができればいいです。

1
2
3
4
5
6
7
8
irb
2.3.0 :001 > require_relative "coder"
2.3.0 :002 > coder = Coder.new
 => #<Coder:0x007ffa4a04b1d0 @key="\xE4\xC6\xDF\x80\xE7\x81k\x7F\b@\ezo\x18@~U}\xE9\xA9\xE4++\xAE\x92X\x81\xE8\tu\xD6(", @iv="S\x8C\xC6\xB8\x97l\xACu\xD4O\xB7\xEF\xAA\x11\xCB\xD3", @encoder=#<OpenSSL::Cipher:0x007ffa4a04b108>, @decoder=#<OpenSSL::Cipher:0x007ffa4a04b0e0>>
2.3.0 :003 > encoded = coder.encode("test12345")
 => ",\x81\xFD\xEE\v\x91fr\xA1eC\xECR:\x00\xF0"
2.3.0 :004 > coder.decode(encoded)
 => "test12345"

後は前回残した TODO のところにこの Coder を適用すればいいです。

ここでちょっとハマったところは、EventMachine::Connection#send_data はメッセージをまるごと送信するとは限らないです。途切れ途切れになるかもしれません。
暗号化しない場合は特に問題ないだけど、暗号化すると、どこからどこまでは1つのメッセージなのかがわからないと、ちゃんと複合できなくなります。
だから人為的に区切りのものをメッセージの間に入れることになりました。

1
2
3
4
5
def send_encoded_data(data)
  return if data.nil? || data.empty?
  send_data(@coder.encode(data))
  send_data("DRECOMADVENTCALENDAR")  # 文字列にするかバイナリにするかはお好きにどうぞ
end

通信内容はこんな感じになります。

1
2
3
4
5
6
HOST:PORT (Encoded)
DRECOMADVENTCALENDAR <- Delimiter
DATA (Encoded)
DRECOMADVENTCALENDAR <- Delimiter
DATA (Encoded)
DRECOMADVENTCALENDAR <- Delimiter

複合するところも一旦バッファに入れて、区切りのところまで切り取って順番に復号します。

1
2
3
4
5
6
7
8
9
10
def receive_data(data)
  return if data.nil? || data.empty?
  @buffer << data
  loop do
    fore, rest = @buffer.split("DRECOMADVENTCALENDAR", 2)
    break unless rest
    server.send_data(@coder.decode(fore))
    @buffer = rest
  end
end

これで通信が暗号化され、安心で使えるようになりました。\(^▽^)/
作りはまだ雑だけど、もう実用できると思います。

具体的なコードはまた Github のほうへどうぞ。