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 +1 -1
- data/README.md +8 -13
- data/crypto_laser.gemspec +1 -1
- data/crypto_laser.jpeg +0 -0
- data/lib/crypto_laser.rb +54 -35
- data/spec/lib/crypto_laser_spec.rb +59 -6
- metadata +2 -1
data/Gemfile.lock
CHANGED
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
|
-
|
11
|
-
|
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
|
-
|
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
|
-
|
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
|
+

|
data/crypto_laser.gemspec
CHANGED
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
|
5
|
-
#
|
4
|
+
# Simple, cross-platform library for symmetric
|
5
|
+
# authenticated encryption.
|
6
6
|
#
|
7
|
-
#
|
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
|
-
#
|
10
|
-
#
|
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
|
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
|
-
|
25
|
+
def encrypt(plain_text)
|
26
|
+
code = default_algorithm_code
|
24
27
|
|
25
|
-
cipher = OpenSSL::Cipher.new(
|
28
|
+
cipher = OpenSSL::Cipher.new(cipher_algorithms[code])
|
26
29
|
cipher.encrypt
|
27
|
-
cipher.key = enc_key
|
28
|
-
|
29
|
-
cipher.iv =
|
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 +
|
33
|
-
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
|
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
|
-
|
44
|
-
algorithm = CryptoLaser.algorithms[code]
|
44
|
+
algorithm = cipher_algorithms[cipher_text[0,2]]
|
45
45
|
raise "Invalid algorithm code." unless algorithm
|
46
46
|
|
47
|
-
|
48
|
-
|
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
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
cipher = OpenSSL::Cipher.new(algorithm)
|
51
|
+
cipher.decrypt
|
52
|
+
cipher.key = @enc_key
|
53
|
+
cipher.iv = cipher_text[2...18]
|
55
54
|
|
56
|
-
|
55
|
+
cipher.update(cipher_text[18...-32]) + cipher.final
|
57
56
|
end
|
58
57
|
|
59
|
-
|
58
|
+
private
|
59
|
+
|
60
|
+
def cipher_algorithms
|
60
61
|
{ "V1" => 'AES-256-CBC' }
|
61
62
|
end
|
62
63
|
|
63
|
-
def
|
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
|
-
|
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
|
-
|
19
|
+
subject { instance.encrypt(plain_text) }
|
14
20
|
|
15
|
-
|
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
|
-
|
18
|
-
|
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 {
|
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
|
+
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
|