crypto_laser 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- crypto_laser (0.0.4)
4
+ crypto_laser (0.0.5)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
data/README.md CHANGED
@@ -1,23 +1,18 @@
1
1
  Crypto Laser
2
2
  ============
3
3
 
4
- Simple library for symmetric authenticated encryption.
4
+ Simple, cross-platform library for symmetric authenticated encryption.
5
5
 
6
6
  key = SecureRandom.random_bytes(64)
7
7
  cipher_text = CryptoLaser.encrypt(key, "my secret message")
8
8
  plain_text = CryptoLaser.decrypt(key, cipher_text)
9
9
 
10
- Most of the work is done by the aead gem, which itself relies
11
- on OpenSSL. Since many users, ourselves included, are on
12
- OS X, this library uses AES-256-CBC-HMAC-SHA-256 as the cipher
13
- and MAC.
10
+ Note that the key must be a raw byte array exactly 64 bytes long.
11
+ The cipher texts returned by encrypt are Base64-encoded.
14
12
 
15
- Additional features provided by this library are:
13
+ The heavy lifting is done by Ruby's OpenSSL, so we use AES-256-CBC as
14
+ the cipher and HMAC-SHA-256 as the MAC. These algorithms should be
15
+ available on all Ruby 1.9 installations (including those on OS X,
16
+ Windows, and Linux).
16
17
 
17
- - Nonce management. No one knows what a nonce is, so this
18
- library just takes care of that for you.
19
- - Base64 encoding of ciphertexts for easy portability.
20
- - The value returned by encrypt includes the nonce and
21
- the algorithm used to create the ciphertext. The
22
- algorithm is authenticated, allowing for later cipher
23
- suite negotiation should AES-256-CBC prove unreliable.
18
+ ![alt text](https://github.com/goodsearch/crypto_laser/raw/master/crypto_laser.jpeg "pew pew pew")
data/crypto_laser.gemspec CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "crypto_laser"
6
- s.version = "0.0.4"
6
+ s.version = "0.0.5"
7
7
  s.authors = %w(Goodsearch)
8
8
  s.email = %w(dev@goodsearch.com)
9
9
  s.homepage = "http://www.goodsearch.com"
data/crypto_laser.jpeg ADDED
Binary file
data/lib/crypto_laser.rb CHANGED
@@ -1,67 +1,86 @@
1
1
  require 'openssl'
2
2
  require 'base64'
3
3
 
4
- # Simple library for authenticated encryption. Most of the work
5
- # is done by the aead gem, which itself punts to OpenSSL.
4
+ # Simple, cross-platform library for symmetric
5
+ # authenticated encryption.
6
6
  #
7
- # Additional features provided by this library are:
7
+ # key = SecureRandom.random_bytes(64)
8
+ # cipher_text = CryptoLaser.encrypt(key, "my secret message")
9
+ # plain_text = CryptoLaser.decrypt(key, cipher_text)
8
10
  #
9
- # - Nonce management. No one knows what a nonce is, so this
10
- # library just takes of that for you.
11
- # - Base64 encoding of ciphertexts (since we want to use the
12
- # encrypted values in config files)
13
- # - The value returned by encrypt includes the nonce and
14
- # the algorithm used to create the ciphertext (so we can
15
- # upgrade to a stronger algorithm later if need be)
11
+ # Note that the key must be a raw byte array exactly 64 bytes long.
12
+ # The cipher texts returned by encrypt are Base64-encoded.
16
13
 
14
+ # The heavy lifting is done by Ruby's OpenSSL, so we use AES-256-CBC as
15
+ # the cipher and HMAC-SHA-256 as the MAC. These algorithms should be
16
+ # available on all Ruby 1.9 installations (including those on OS X,
17
+ # Windows, and Linux).
17
18
  class CryptoLaser
18
19
 
19
- def self.encrypt(key, plain_text)
20
- enc_key = key[0...32]
21
- mac_key = key[32...64]
20
+ def initialize(key)
21
+ @enc_key = key[0...32]
22
+ @mac_key = key[32...64]
23
+ end
22
24
 
23
- code = CryptoLaser.default_algorithm_code
25
+ def encrypt(plain_text)
26
+ code = default_algorithm_code
24
27
 
25
- cipher = OpenSSL::Cipher.new(CryptoLaser.algorithms[code])
28
+ cipher = OpenSSL::Cipher.new(cipher_algorithms[code])
26
29
  cipher.encrypt
27
- cipher.key = enc_key
28
- nonce = cipher.random_iv
29
- cipher.iv = nonce
30
+ cipher.key = @enc_key
31
+ iv = cipher.random_iv
32
+ cipher.iv = iv
30
33
  cipher_text = cipher.update(plain_text) + cipher.final
31
34
 
32
- text_to_mac = code + nonce + cipher_text
33
- mac = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), mac_key, text_to_mac)
35
+ text_to_mac = code + iv + cipher_text
36
+ mac = mac(text_to_mac)
34
37
 
35
38
  Base64.strict_encode64(text_to_mac + mac).chomp
36
39
  end
37
40
 
38
- def self.decrypt(key, base64_cipher_text)
39
- enc_key = key[0...32]
40
- mac_key = key[32...64]
41
+ def decrypt(base64_cipher_text)
41
42
  cipher_text = Base64.decode64(base64_cipher_text)
42
43
 
43
- code = cipher_text[0,2]
44
- algorithm = CryptoLaser.algorithms[code]
44
+ algorithm = cipher_algorithms[cipher_text[0,2]]
45
45
  raise "Invalid algorithm code." unless algorithm
46
46
 
47
- text_to_mac = cipher_text[0...-32]
48
- mac = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha256'), mac_key, text_to_mac)
49
- raise "MAC check failed" unless mac == cipher_text[-32..-1]
47
+ mac = mac(cipher_text[0...-32])
48
+ raise "MAC check failed" unless equal_macs(mac, cipher_text[-32..-1])
50
49
 
51
- decipher = OpenSSL::Cipher.new(CryptoLaser.algorithms[code])
52
- decipher.decrypt
53
- decipher.key = enc_key
54
- decipher.iv = cipher_text[2...18]
50
+ cipher = OpenSSL::Cipher.new(algorithm)
51
+ cipher.decrypt
52
+ cipher.key = @enc_key
53
+ cipher.iv = cipher_text[2...18]
55
54
 
56
- decipher.update(cipher_text[18...-32]) + decipher.final
55
+ cipher.update(cipher_text[18...-32]) + cipher.final
57
56
  end
58
57
 
59
- def self.algorithms
58
+ private
59
+
60
+ def cipher_algorithms
60
61
  { "V1" => 'AES-256-CBC' }
61
62
  end
62
63
 
63
- def self.default_algorithm_code
64
+ def mac_algorithms
65
+ { "V1" => 'SHA256' }
66
+ end
67
+
68
+ def default_algorithm_code
64
69
  "V1"
65
70
  end
66
71
 
72
+ def mac(text)
73
+ algorithm = mac_algorithms[text[0...2]]
74
+ OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(algorithm), @mac_key, text)
75
+ end
76
+
77
+ def sha256(x)
78
+ OpenSSL::Digest::SHA256.digest(x)
79
+ end
80
+
81
+ # Guards against timing attacks, just in case.
82
+ def equal_macs(mac1, mac2)
83
+ sha256(mac1) == sha256(mac2)
84
+ end
85
+
67
86
  end
@@ -1,28 +1,50 @@
1
1
  require 'spec_helper'
2
2
  require 'crypto_laser'
3
+ require 'securerandom'
3
4
 
4
5
  describe CryptoLaser do
5
6
 
7
+ raw_cipher_text = "V1\x97\x88\xF3\x0Ei\x84\x99\xC7 OZ2\xCA\v\x873\x10\xD5jf\xDD\x84\xCA?~\x87\xEE|\x92\xD7\xB8r\x8D*\x02\xB5\x82\xE9\xFD>\xB5\x80Fh.kuO\x91\xE5\xF7&\xCC\xB9E\xB4?c\xE1\xE2b%fM"
8
+
9
+ let(:instance) { described_class.new(key) }
10
+
6
11
  let(:key) { "\"/\\xE0x5\\x9A\\xE9\\x82\\xB8p \\xED^\\xFFX\\xF6\\xB3}\\xB9bR\\xCF\\xDAdH\\xE4\\x9D\\xB5\\xC2r\\x98\\xD3\\xFC\"" }
7
12
  let(:nonce) { "\x97\x88\xF3\x0Ei\x84\x99\xC7 OZ2\xCA\v\x873" }
8
- let(:cipher_text) { "VjGXiPMOaYSZxyBPWjLKC4czENVqZt2Eyj9+h+58kte4co0qArWC6f0+tYBGaC5rdU+R5fcmzLlFtD9j4eJiJWZN" }
13
+
14
+ let(:cipher_text) { Base64.strict_encode64(raw_cipher_text).chomp }
9
15
  let(:plain_text) { "ZOMG PONIES" }
10
16
 
11
17
  describe "#encrypt" do
12
18
 
13
- before { OpenSSL::Cipher.any_instance.stub(:random_iv).and_return nonce }
19
+ subject { instance.encrypt(plain_text) }
14
20
 
15
- subject { described_class.encrypt(key, plain_text) }
21
+ context "simple encryption" do
22
+ before { OpenSSL::Cipher.any_instance.stub(:random_iv).and_return nonce }
23
+ it "encrypts plain text" do
24
+ subject.should == cipher_text
25
+ end
26
+ end
16
27
 
17
- it "encrypts plain text" do
18
- subject.should == cipher_text
28
+ context "multiple encryptions of the same plaintext" do
29
+ it "results in different ciphertexts" do
30
+ cipher1 = instance.encrypt(plain_text)
31
+ cipher2 = instance.encrypt(plain_text)
32
+ cipher1.should_not == cipher2
33
+ end
34
+ end
35
+
36
+ context "empty plaintext" do
37
+ subject { instance.encrypt("") }
38
+ it "fails fast" do
39
+ expect { subject }.to raise_error /data must not be empty/i
40
+ end
19
41
  end
20
42
 
21
43
  end
22
44
 
23
45
  describe "#decrypt" do
24
46
 
25
- subject { described_class.decrypt(key, cipher_text) }
47
+ subject { instance.decrypt(cipher_text) }
26
48
 
27
49
  it "decrypts cipher text" do
28
50
  subject.should == plain_text
@@ -34,6 +56,12 @@ describe CryptoLaser do
34
56
  end
35
57
  end
36
58
 
59
+ shared_examples_for "raises because the MAC check fails" do
60
+ specify do
61
+ expect { subject }.to raise_error /mac check failed/i
62
+ end
63
+ end
64
+
37
65
  context "when the algorithm code is invalid" do
38
66
  let(:cipher_text) { Base64.encode64("V2").chomp }
39
67
  it_should_behave_like "raises because the code is invalid"
@@ -44,6 +72,31 @@ describe CryptoLaser do
44
72
  it_should_behave_like "raises because the code is invalid"
45
73
  end
46
74
 
75
+ context "when an adversary alters the ciphertext" do
76
+ (2...raw_cipher_text.size).each do |i|
77
+ context "when an adversary alters byte #{i - 2} of the cipher text" do
78
+ let(:cipher_text) do
79
+ raw_cipher_text[i] = (raw_cipher_text[i].ord ^ 1).chr
80
+ Base64.strict_encode64(raw_cipher_text)
81
+ end
82
+ it_should_behave_like "raises because the MAC check fails"
83
+ end
84
+ end
85
+ end
86
+
87
+ end
88
+
89
+ context "random large string round-trips" do
90
+
91
+ it "correctly decrypts" do
92
+ 1000.times do
93
+ len = SecureRandom.random_number(8192) + 1
94
+ plain_text = SecureRandom.random_bytes(len)
95
+ cipher_text = instance.encrypt(plain_text)
96
+ instance.decrypt(cipher_text).should == plain_text
97
+ end
98
+ end
99
+
47
100
  end
48
101
 
49
102
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crypto_laser
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -65,6 +65,7 @@ files:
65
65
  - README.md
66
66
  - Rakefile
67
67
  - crypto_laser.gemspec
68
+ - crypto_laser.jpeg
68
69
  - lib/crypto_laser.rb
69
70
  - lib/tasks/decrypt.rake
70
71
  - lib/tasks/encrypt.rake