crypto_laser 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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