sjcl 0.0.1 → 1.0.0
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.
- checksums.yaml +4 -4
- data/README.md +17 -7
- data/Rakefile +1 -1
- data/lib/sjcl.rb +1 -1
- data/lib/sjcl/pbkdf2.rb +2 -158
- data/lib/sjcl/version.rb +1 -1
- data/spec/aes_spec.rb +6 -6
- data/spec/spec_helper.rb +6 -2
- metadata +12 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9bc27222ca1e75a41a1db9f11f8cb007b6a79874
|
4
|
+
data.tar.gz: 999c3ec4e08aef5f06daa2d60f6756f93f94331a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 716feceacd1b35b785cb122cf53aeb2735292b9e0765da504779fa5ff49fd13c3b4411ad9bb8215d753fcb8b0f3ff5c44270d4510cdd7157c5e350304f097e53
|
7
|
+
data.tar.gz: 1c8821376b8969072246b53058dc163b5250de237ed80aca1954d33f88f8f332993f9c478439d4b774ce7ad5c6a597084c5d743328a2f88daea865518e2f17c0
|
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
A Ruby gem to interop with SJCL in AES-CCM mode.
|
5
5
|
|
6
|
-
Defaults to 256 bit AES in CCM mode with
|
6
|
+
Defaults to 256 bit AES in CCM mode with 100_000 iterations PBKDF2
|
7
7
|
|
8
8
|
### Install
|
9
9
|
|
@@ -14,14 +14,24 @@ gem install sjcl
|
|
14
14
|
enc = SJCL.encrypt('password', "Something to encrypt")
|
15
15
|
dec = SJCL.decrypt('password', enc)
|
16
16
|
|
17
|
-
|
17
|
+
# Custom number of PBKDF2 iterations
|
18
|
+
enc = SJCL.encrypt('password', "Something to encrypt", {:iter => 10_000})
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
### Usage
|
21
|
+
|
22
|
+
dec = SJCL.decrypt('password', enc)
|
23
|
+
|
24
|
+
### Dev Goals
|
25
|
+
|
26
|
+
- Should be 100% compatible with SJCL Javascript library in AES-CCM mode
|
27
|
+
- Should not be dependent upon OpenSSL having been compiles with AES-CCM-256 support (May be slower)
|
22
28
|
|
23
29
|
### TODO
|
24
30
|
|
25
|
-
- More modes
|
26
31
|
- Test interop with node module directly
|
27
|
-
|
32
|
+
|
33
|
+
### Changelog
|
34
|
+
|
35
|
+
- 1.0.0
|
36
|
+
- Update to use OpenSSL PBKDF2 function for increased speed
|
37
|
+
- Increase default iterations to 100,000
|
data/Rakefile
CHANGED
data/lib/sjcl.rb
CHANGED
data/lib/sjcl/pbkdf2.rb
CHANGED
@@ -5,166 +5,10 @@ module SJCL
|
|
5
5
|
module Misc
|
6
6
|
|
7
7
|
def self.pbkdf2(password, salt, iter, length)
|
8
|
-
|
9
|
-
|
10
|
-
:salt=>salt,
|
11
|
-
:key_length => length/8,
|
12
|
-
:iterations=>iter).hex_string
|
13
|
-
SJCL::Codec::Hex.toBits(key)
|
8
|
+
key = OpenSSL::PKCS5.pbkdf2_hmac(password, Base64.decode64(salt), iter, length/8, 'SHA256')
|
9
|
+
SJCL::Codec::Hex.toBits(key.unpack('H*')[0])
|
14
10
|
end
|
15
11
|
|
16
12
|
end
|
17
13
|
end
|
18
14
|
|
19
|
-
|
20
|
-
# Pilfered from https://github.com/emerose and updated to Ruby >2.0
|
21
|
-
class SJCL::PBKDF2
|
22
|
-
def initialize(opts={})
|
23
|
-
@hash_function = OpenSSL::Digest.new("sha256")
|
24
|
-
|
25
|
-
# override with options
|
26
|
-
opts.each_key do |k|
|
27
|
-
if self.respond_to?("#{k}=")
|
28
|
-
self.send("#{k}=", opts[k])
|
29
|
-
else
|
30
|
-
raise ArgumentError, "Argument '#{k}' is not allowed"
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
yield self if block_given?
|
35
|
-
|
36
|
-
# set this to the default if nothing was given
|
37
|
-
@key_length ||= @hash_function.size
|
38
|
-
|
39
|
-
# make sure the relevant things got set
|
40
|
-
raise ArgumentError, "password not set" if @password.nil?
|
41
|
-
raise ArgumentError, "salt not set" if @salt.nil?
|
42
|
-
raise ArgumentError, "iterations not set" if @iterations.nil?
|
43
|
-
end
|
44
|
-
attr_reader :key_length, :hash_function, :iterations, :salt, :password
|
45
|
-
|
46
|
-
def key_length=(l)
|
47
|
-
raise ArgumentError, "key too short" if l < 1
|
48
|
-
raise ArgumentError, "key too long" if l > ((2**32 - 1) * @hash_function.size)
|
49
|
-
@value = nil
|
50
|
-
@key_length = l
|
51
|
-
end
|
52
|
-
|
53
|
-
def hash_function=(h)
|
54
|
-
@value = nil
|
55
|
-
@hash_function = find_hash(h)
|
56
|
-
end
|
57
|
-
|
58
|
-
def iterations=(i)
|
59
|
-
raise ArgumentError, "iterations can't be less than 1" if i < 1
|
60
|
-
@value = nil
|
61
|
-
@iterations = i
|
62
|
-
end
|
63
|
-
|
64
|
-
def salt=(s)
|
65
|
-
@value = nil
|
66
|
-
@salt = s
|
67
|
-
end
|
68
|
-
|
69
|
-
def password=(p)
|
70
|
-
@value = nil
|
71
|
-
@password = p
|
72
|
-
end
|
73
|
-
|
74
|
-
def value
|
75
|
-
calculate! if @value.nil?
|
76
|
-
@value
|
77
|
-
end
|
78
|
-
|
79
|
-
alias bin_string value
|
80
|
-
|
81
|
-
def hex_string
|
82
|
-
bin_string.unpack("H*").first
|
83
|
-
end
|
84
|
-
|
85
|
-
# return number of milliseconds it takes to complete one iteration
|
86
|
-
def benchmark(iters = 400000)
|
87
|
-
iter_orig = @iterations
|
88
|
-
@iterations=iters
|
89
|
-
start = Time.now
|
90
|
-
calculate!
|
91
|
-
time = Time.now - start
|
92
|
-
@iterations = iter_orig
|
93
|
-
return (time/iters)
|
94
|
-
end
|
95
|
-
|
96
|
-
protected
|
97
|
-
|
98
|
-
# finds and instantiates, if necessary, a hash function
|
99
|
-
def find_hash(hash)
|
100
|
-
case hash
|
101
|
-
when Class
|
102
|
-
# allow people to pass in classes to be instantiated
|
103
|
-
# (eg, pass in OpenSSL::Digest::SHA1)
|
104
|
-
hash = find_hash(hash.new)
|
105
|
-
when Symbol
|
106
|
-
# convert symbols to strings and see if OpenSSL::Digest can make sense of
|
107
|
-
hash = find_hash(hash.to_s)
|
108
|
-
when String
|
109
|
-
# if it's a string, first strip off any leading 'hmacWith' (which is implied)
|
110
|
-
hash.gsub!(/^hmacWith/i,'')
|
111
|
-
# see if the OpenSSL lib understands it
|
112
|
-
hash = OpenSSL::Digest.new(hash)
|
113
|
-
when OpenSSL::Digest
|
114
|
-
when OpenSSL::Digest::Digest
|
115
|
-
# ok
|
116
|
-
else
|
117
|
-
raise TypeError, "Unknown hash type: #{hash.class}"
|
118
|
-
end
|
119
|
-
hash
|
120
|
-
end
|
121
|
-
|
122
|
-
# the pseudo-random function defined in the spec
|
123
|
-
def prf(data)
|
124
|
-
OpenSSL::HMAC.digest(@hash_function, @password, data)
|
125
|
-
end
|
126
|
-
|
127
|
-
# this is a translation of the helper function "F" defined in the spec
|
128
|
-
def calculate_block(block_num)
|
129
|
-
# u_1:
|
130
|
-
u = prf(salt+[block_num].pack("N"))
|
131
|
-
ret = u
|
132
|
-
# u_2 through u_c:
|
133
|
-
2.upto(@iterations) do
|
134
|
-
# calculate u_n
|
135
|
-
u = prf(u)
|
136
|
-
# xor it with the previous results
|
137
|
-
ret = str_xor(ret, u)
|
138
|
-
end
|
139
|
-
ret
|
140
|
-
end
|
141
|
-
|
142
|
-
# the bit that actually does the calculating
|
143
|
-
def calculate!
|
144
|
-
# how many blocks we'll need to calculate (the last may be truncated)
|
145
|
-
blocks_needed = (@key_length.to_f / @hash_function.size).ceil
|
146
|
-
# reset
|
147
|
-
@value = ""
|
148
|
-
# main block-calculating loop:
|
149
|
-
1.upto(blocks_needed) do |block_num|
|
150
|
-
@value << calculate_block(block_num)
|
151
|
-
end
|
152
|
-
# truncate to desired length:
|
153
|
-
@value = @value.slice(0,@key_length)
|
154
|
-
@value
|
155
|
-
end
|
156
|
-
|
157
|
-
def str_xor(str1, str2)
|
158
|
-
raise ArgumentError, "Can't bitwise-XOR a String with a non-String" \
|
159
|
-
unless str1.kind_of? String
|
160
|
-
raise ArgumentError, "Can't bitwise-XOR strings of different length" \
|
161
|
-
unless str2.length == str1.length
|
162
|
-
result = "".encode("ASCII-8BIT")
|
163
|
-
o_bytes = str2.bytes.to_a
|
164
|
-
str1.bytes.each_with_index do |c, i|
|
165
|
-
result << (c ^ o_bytes[i])
|
166
|
-
end
|
167
|
-
result
|
168
|
-
end
|
169
|
-
|
170
|
-
end
|
data/lib/sjcl/version.rb
CHANGED
data/spec/aes_spec.rb
CHANGED
@@ -6,15 +6,15 @@ describe "the SJCL AES cipher" do
|
|
6
6
|
expectedEnc = [-1029611070, -1587456955, 1398035525, 17593584058368, -473824721, 1118000746, 301400623, 1117979183, -340453629, -1458159255, -1193196730, -96321175, -1019810258, 1780526919, -759043071, 679718248, 665037594, 1300431965, -1623005092, -1212070604, 1338880947, 38713326, -1660133454, 718084742, -764414122, -803127112, 1294802698, 1742734732, 1929679827, -1557802133, -301413279, -1981232659, 1759102580, -872671969, 636803454, -1407446893, -283960603, 619646970, 18601604, -1391999465, 480766576, 944301450, 961753870, -1806371559]
|
7
7
|
expectedDec = [480766576, -1806371559, 961753870, 944301450, -286319329, -615496010, 2062433761, -793409572, -2072105771, -1581352105, -1436829123, 1046125251, -1529599379, 199606634, -1811865346, -1171999210, -614676899, -1612389996, 774215400, 519085179, 1932300365, -1312721028, 819268243, -978560474, -1259908556, -2129363473, -176598859, -1233073557, 8079113, 1953312090, 1140431582, 40343647, -1507275254, 932493188, 1100874369, 35446614, 1689034073, 1980413189, 1132649943, -1540091556, -1029611070, 17593584058368, 1398035525, -1587456955]
|
8
8
|
cipher = SJCL::Cipher::AES.new([-1029611070, -1587456955, 1398035525, 17593584058368])
|
9
|
-
SJCL::BitArray.compare(cipher.key[0], expectedEnc).should
|
10
|
-
SJCL::BitArray.compare(cipher.key[1], expectedDec).should
|
9
|
+
SJCL::BitArray.compare(cipher.key[0], expectedEnc).should be_truthy
|
10
|
+
SJCL::BitArray.compare(cipher.key[1], expectedDec).should be_truthy
|
11
11
|
end
|
12
12
|
it "should match at 256bits" do
|
13
13
|
expectedEnc = [1181708080, 1181708080, 1181708080, 1181708080, 1181708080, 1181708080, 1181708080, 1181708080, -272143510, -1448606630, -272143510, -1448606630, -1783784050, -742198594, -1783784050, -742198594, -934361844, 1642512726, -1910396356, 663334502, 1493858749, -1966568701, 526718605, -861412301, -1876490681, -238958831, 2145414445, 1483326283, 871585550, -1187259379, -1503737216, 1794715315, 18246469, -254301100, -1892171399, -681704910, 1034625069, -2070873056, 583939744, 1211570195, -1950673385, 2070723139, -195331270, 587561224, 465606173, -1622119875, -1113695075, -173456242, 2023755761, 63747506, -141046136, -728582272, 1401896912, -857778707, 1900087664, -2065150466, -1555843922, -1601240804, 1461243796, -2088077292]
|
14
14
|
expectedDec = [-1555843922, -2088077292, 1461243796, -1601240804, -349970150, 681329711, -584960127, 1016916195, 431432362, 457480884, -564876537, -1173387270, 1389592494, -172067922, -507584670, -675345927, 199160848, -988794445, 1683696893, -1548180144, 1369923852, 335588556, 906090139, -2056489385, 1985048176, -1588912818, -941389395, -1469689536, 507794320, 570522199, -1284661044, -724887717, -1929777810, 1722324195, 1871042797, -566804688, 1011165639, -1855137125, 1738979223, -896580405, -363759219, 153963534, -1313675299, 1389285982, -1389759652, -154505972, -1389759652, -154505972, -478399101, -1197495341, -478399101, -1197495341, 1541643856, 1541643856, 1541643856, 1541643856, 1181708080, 1181708080, 1181708080, 1181708080]
|
15
15
|
cipher = SJCL::Cipher::AES.new(SJCL::Codec::UTF8String.toBits("Foo0Foo0Foo0Foo0Foo0Foo0Foo0Foo0"))
|
16
|
-
SJCL::BitArray.compare(cipher.key[0], expectedEnc).should
|
17
|
-
SJCL::BitArray.compare(cipher.key[1], expectedDec).should
|
16
|
+
SJCL::BitArray.compare(cipher.key[0], expectedEnc).should be_truthy
|
17
|
+
SJCL::BitArray.compare(cipher.key[1], expectedDec).should be_truthy
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -25,11 +25,11 @@ describe "the SJCL AES cipher" do
|
|
25
25
|
it "should encrypt data" do
|
26
26
|
expectedEnc = [1991380212, -38165922, 194830393, 500234942] # Taken from SJCL JS
|
27
27
|
enc = cipher.encrypt(data)
|
28
|
-
SJCL::BitArray.compare(enc, expectedEnc).should
|
28
|
+
SJCL::BitArray.compare(enc, expectedEnc).should be_truthy
|
29
29
|
end
|
30
30
|
it "should decrypt data" do
|
31
31
|
dec = cipher.decrypt(cipher.encrypt(data))
|
32
|
-
SJCL::BitArray.compare(data, dec).should
|
32
|
+
SJCL::BitArray.compare(data, dec).should be_truthy
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'bundler/setup'
|
3
3
|
require 'rspec'
|
4
|
-
require 'rspec/autorun'
|
5
4
|
|
6
5
|
require 'sjcl'
|
7
6
|
|
8
7
|
RSpec.configure do |config|
|
9
|
-
|
8
|
+
config.mock_with :rspec do |c|
|
9
|
+
c.syntax = [:should, :expect]
|
10
|
+
end
|
11
|
+
config.expect_with :rspec do |c|
|
12
|
+
c.syntax = [:should, :expect]
|
13
|
+
end
|
10
14
|
end
|
metadata
CHANGED
@@ -1,41 +1,41 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sjcl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mark Percival
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-01-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '>='
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '>='
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rspec
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - '>='
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - '>='
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
41
|
description:
|
@@ -45,8 +45,8 @@ executables: []
|
|
45
45
|
extensions: []
|
46
46
|
extra_rdoc_files: []
|
47
47
|
files:
|
48
|
-
-
|
49
|
-
-
|
48
|
+
- .gitignore
|
49
|
+
- .travis.yml
|
50
50
|
- Gemfile
|
51
51
|
- LICENSE
|
52
52
|
- README.md
|
@@ -82,27 +82,18 @@ require_paths:
|
|
82
82
|
- lib
|
83
83
|
required_ruby_version: !ruby/object:Gem::Requirement
|
84
84
|
requirements:
|
85
|
-
- -
|
85
|
+
- - '>='
|
86
86
|
- !ruby/object:Gem::Version
|
87
87
|
version: '0'
|
88
88
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
89
|
requirements:
|
90
|
-
- -
|
90
|
+
- - '>='
|
91
91
|
- !ruby/object:Gem::Version
|
92
92
|
version: '0'
|
93
93
|
requirements: []
|
94
94
|
rubyforge_project: sjcl
|
95
|
-
rubygems_version: 2.
|
95
|
+
rubygems_version: 2.0.2
|
96
96
|
signing_key:
|
97
97
|
specification_version: 4
|
98
98
|
summary: A Ruby library for interopping with SJCL's AES crypto
|
99
|
-
test_files:
|
100
|
-
- spec/aes_spec.rb
|
101
|
-
- spec/bit_array_spec.rb
|
102
|
-
- spec/ccm_spec.rb
|
103
|
-
- spec/code_base64_spec.rb
|
104
|
-
- spec/codec_string_spec.rb
|
105
|
-
- spec/codex_hex_spec.rb
|
106
|
-
- spec/integration_spec.rb
|
107
|
-
- spec/pbkdf2_spec.rb
|
108
|
-
- spec/spec_helper.rb
|
99
|
+
test_files: []
|