scl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/README.md +71 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/scl +124 -0
- data/lib/scl/aes.rb +28 -0
- data/lib/scl/control/aes.rb +51 -0
- data/lib/scl/control/controller.rb +96 -0
- data/lib/scl/control/controller_module.rb +102 -0
- data/lib/scl/control/dh.rb +84 -0
- data/lib/scl/control/digest.rb +90 -0
- data/lib/scl/control/output.rb +14 -0
- data/lib/scl/control/rsa.rb +161 -0
- data/lib/scl/control/sss.rb +48 -0
- data/lib/scl/control.rb +14 -0
- data/lib/scl/dh.rb +52 -0
- data/lib/scl/digest.rb +29 -0
- data/lib/scl/formats/auto.rb +23 -0
- data/lib/scl/formats/base64.rb +12 -0
- data/lib/scl/formats/binary.rb +11 -0
- data/lib/scl/formats/format.rb +44 -0
- data/lib/scl/formats/hex.rb +11 -0
- data/lib/scl/formats/qrcode.rb +11 -0
- data/lib/scl/formats/stdout.rb +11 -0
- data/lib/scl/formats/wordlist.txt +235886 -0
- data/lib/scl/formats/words.rb +23 -0
- data/lib/scl/rsa.rb +72 -0
- data/lib/scl/secret_share.rb +122 -0
- data/lib/scl/version.rb +3 -0
- data/lib/scl.rb +12 -0
- data/scl.gemspec +25 -0
- metadata +120 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
module Scl
|
2
|
+
class Words < Format
|
3
|
+
|
4
|
+
WORDS = IO.read(File.expand_path(__FILE__+"/../wordlist.txt")).split("\n")
|
5
|
+
INDICES = Hash[WORDS.map.with_index{|w, i|[w,i]}]
|
6
|
+
|
7
|
+
def encode(data)
|
8
|
+
data.bytes.each_slice(2).map do |b1, b2|
|
9
|
+
WORDS[(b1 << 8) + (b2 || 0)]
|
10
|
+
end.join(' ')
|
11
|
+
end
|
12
|
+
|
13
|
+
def decode(data)
|
14
|
+
bytes = data.split(' ').each.map do |word|
|
15
|
+
bytes = INDICES[word]
|
16
|
+
b2 = bytes & 255
|
17
|
+
b1 = (bytes >> 8) & 255
|
18
|
+
[b1, b2]
|
19
|
+
end.flatten
|
20
|
+
bytes.map(&:chr).join
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/scl/rsa.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
module Scl
|
2
|
+
class RSA
|
3
|
+
attr_reader :private, :public
|
4
|
+
def initialize(file: nil, public: nil, private: nil, input_encoder: Format::BINARY, output_encoder: Format::BINARY)
|
5
|
+
if file
|
6
|
+
@public = OpenSSL::PKey::RSA.new(encoder.decode(IO.read("#{file}.pub"))) if File.exists?("#{file}.pub")
|
7
|
+
@private = OpenSSL::PKey::RSA.new(encoder.decode(IO.read("#{file}.priv"))) if File.exists?("#{file}.priv")
|
8
|
+
else
|
9
|
+
@public = public
|
10
|
+
@private = private
|
11
|
+
end
|
12
|
+
@public = Key.new(@public)
|
13
|
+
@private = Key.new(@private)
|
14
|
+
end
|
15
|
+
|
16
|
+
def save(dir, name='rsa-keypair')
|
17
|
+
IO.write(File.join(dir, "#{name}.pub"), output_encoder.encode(@public.export))
|
18
|
+
IO.write(File.join(dir, "#{name}.priv"), output_encoder.encode(@private.export))
|
19
|
+
dir
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.generate(key_size)
|
23
|
+
rsa_pair = OpenSSL::PKey::RSA.new(key_size || 2048)
|
24
|
+
RSA.new(public: rsa_pair.public_key, private: rsa_pair)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.encrypt(msg)
|
28
|
+
pair = self.generate
|
29
|
+
[pair.private.export, pair.public.export, pair.private.encrypt(msg)]
|
30
|
+
end
|
31
|
+
|
32
|
+
class Key
|
33
|
+
attr_reader :rsa
|
34
|
+
def initialize(rsa)
|
35
|
+
@rsa = rsa
|
36
|
+
@aes = Scl::AES.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def sign(data)
|
40
|
+
rsa.sign(OpenSSL::Digest::SHA256.new, data)
|
41
|
+
end
|
42
|
+
|
43
|
+
def verify(signature, data)
|
44
|
+
rsa.verify(OpenSSL::Digest::SHA256.new, signature, data)
|
45
|
+
end
|
46
|
+
|
47
|
+
def encrypt(plaintext, key=nil, iv=nil)
|
48
|
+
ciphertext, key, iv = @aes.encrypt(plaintext, key, iv)
|
49
|
+
encrypted_key =
|
50
|
+
case
|
51
|
+
when rsa.private? then rsa.private_encrypt(key)
|
52
|
+
else rsa.public_encrypt(key)
|
53
|
+
end
|
54
|
+
[encrypted_key, iv, ciphertext].join('::')
|
55
|
+
end
|
56
|
+
|
57
|
+
def decrypt(ciphertext)
|
58
|
+
encrypted_key, iv, ciphertext = ciphertext.split('::', 3)
|
59
|
+
decrypted_key =
|
60
|
+
case
|
61
|
+
when rsa.private? then rsa.private_decrypt(encrypted_key)
|
62
|
+
else rsa.public_decrypt(encrypted_key)
|
63
|
+
end
|
64
|
+
@aes.decrypt(ciphertext, decrypted_key, iv)
|
65
|
+
end
|
66
|
+
|
67
|
+
def export
|
68
|
+
rsa.export
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module Scl
|
5
|
+
PRIME = OpenSSL::BN.new("214663014907494254264734401372860550616125566004826012670437788223410219893687")
|
6
|
+
ENCODED_CHUNK_SIZE = 44
|
7
|
+
|
8
|
+
class SecretShare
|
9
|
+
def initialize(minimum, shares, encoder: Format::BASE64)
|
10
|
+
raise "Minimum must be larger than zero and less than shares" unless (0..shares) === minimum
|
11
|
+
@encoder = encoder
|
12
|
+
@minimum = minimum
|
13
|
+
@shares = shares
|
14
|
+
end
|
15
|
+
|
16
|
+
def random()
|
17
|
+
@seen ||= { nil => true}
|
18
|
+
number = Random.rand(1...PRIME) while @seen[number]
|
19
|
+
@seen[number] = true
|
20
|
+
OpenSSL::BN.new(number)
|
21
|
+
end
|
22
|
+
|
23
|
+
def chunks(secret)
|
24
|
+
Enumerator.new do |enum|
|
25
|
+
as_hex = secret.each_byte.map{|b| b.to_s(16).rjust(2, '0') }.join
|
26
|
+
as_hex.chars.each_slice(64) do |slice|
|
27
|
+
chunk = OpenSSL::BN.new(slice.join.ljust(64, ?0), 16)
|
28
|
+
enum << chunk
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def evaluate_polynomial(polynomial, x)
|
34
|
+
polynomial.reverse.reduce(OpenSSL::BN.new(0)) do |mem, value|
|
35
|
+
((mem * x) % PRIME + value) % PRIME
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def generate(secret)
|
40
|
+
polynomials = chunks(secret).map do |chunk|
|
41
|
+
polynomial = [chunk] + (@minimum - 1).times.map do |i|
|
42
|
+
random
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
@shares.times.map do |i|
|
47
|
+
result = ''
|
48
|
+
poly_result = polynomials.map do |polynomial|
|
49
|
+
x = random
|
50
|
+
y = evaluate_polynomial(polynomial, x)
|
51
|
+
result << base64_encode(x)
|
52
|
+
result << base64_encode(y)
|
53
|
+
end
|
54
|
+
@encoder == Format::BASE64 ? result : @encoder.encode(result)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def base64_encode(number)
|
59
|
+
return ::Base64.urlsafe_encode64(number.to_s(16).rjust(64, ?0).scan(/../).map{|x| x.hex.chr}.join)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.base64_decode(number)
|
63
|
+
::Base64.urlsafe_decode64(number).chars.map{|x| "0#{x.ord.to_s(16)}"[-2..-1] }.join.rjust(64, ?0).hex
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.extended_gcd(a, b)
|
67
|
+
abs = ->(v){ v < 0 ? v * -1 : v}
|
68
|
+
last_remainder, remainder = abs[a], abs[b]
|
69
|
+
x, last_x, y, last_y = 0, 1, 1, 0
|
70
|
+
while remainder != 0
|
71
|
+
last_remainder, (quotient, remainder) = remainder, last_remainder./(remainder)
|
72
|
+
x, last_x = last_x - quotient*x, x
|
73
|
+
y, last_y = last_y - quotient*y, y
|
74
|
+
end
|
75
|
+
|
76
|
+
return last_remainder, last_x * (a < 0 ? -1 : 1)
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.mod_inverse(e, p=PRIME)
|
80
|
+
g, x = extended_gcd(e, p)
|
81
|
+
x % p
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.combine(shares, encoder: nil)
|
85
|
+
abs = ->(v){ v < 0 ? v * -1 : v}
|
86
|
+
decoded = shares.map do |share|
|
87
|
+
share = encoder && encoder != Format::BASE64 ? encoder.decode(share) : share
|
88
|
+
share.chars.each_slice(ENCODED_CHUNK_SIZE).map(&:join).each_slice(2).map do |random, polynomial|
|
89
|
+
OpenStruct.new({
|
90
|
+
x: base64_decode(random),
|
91
|
+
y: base64_decode(polynomial)
|
92
|
+
})
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
chunks = decoded[0].length.times.map do |chunk|
|
97
|
+
secret = OpenSSL::BN.new(0)
|
98
|
+
decoded.each do |share|
|
99
|
+
point = share[chunk]
|
100
|
+
numerator, denominator = OpenSSL::BN.new(1), OpenSSL::BN.new(1)
|
101
|
+
decoded.each do |peer|
|
102
|
+
if peer != share
|
103
|
+
current = peer[chunk].x
|
104
|
+
numerator = OpenSSL::BN.new((numerator * -current).to_i % PRIME)
|
105
|
+
denominator = OpenSSL::BN.new((denominator * (point.x - current)).to_i % PRIME)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
working = (point.y * numerator) * mod_inverse(denominator) + PRIME
|
109
|
+
secret = ((secret + working) + 1000 * PRIME) % PRIME
|
110
|
+
end
|
111
|
+
secret
|
112
|
+
end
|
113
|
+
|
114
|
+
hex_data = chunks.map{|chunk| chunk.to_s(16).rjust(64, "0") }.join
|
115
|
+
hex_data.chars.each_slice(64).map(&:join).map do |chunk|
|
116
|
+
chunk.gsub(/(?:00)+$/,'').chars.each_slice(2).map{|pair|
|
117
|
+
pair.join.to_i(16).chr
|
118
|
+
}
|
119
|
+
end.join
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/scl/version.rb
ADDED
data/lib/scl.rb
ADDED
data/scl.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "scl/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "scl"
|
8
|
+
spec.version = Scl::VERSION
|
9
|
+
spec.authors = ["Wouter Coppieters"]
|
10
|
+
spec.email = ["wouter@youdo.co.nz"]
|
11
|
+
|
12
|
+
spec.summary = "Simple crypto library"
|
13
|
+
spec.description = "Simple crypto library"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.15"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "pry-byebug"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: scl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wouter Coppieters
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-11-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.15'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.15'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pry-byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Simple crypto library
|
56
|
+
email:
|
57
|
+
- wouter@youdo.co.nz
|
58
|
+
executables:
|
59
|
+
- scl
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- CODE_OF_CONDUCT.md
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/console
|
69
|
+
- bin/setup
|
70
|
+
- exe/scl
|
71
|
+
- lib/scl.rb
|
72
|
+
- lib/scl/aes.rb
|
73
|
+
- lib/scl/control.rb
|
74
|
+
- lib/scl/control/aes.rb
|
75
|
+
- lib/scl/control/controller.rb
|
76
|
+
- lib/scl/control/controller_module.rb
|
77
|
+
- lib/scl/control/dh.rb
|
78
|
+
- lib/scl/control/digest.rb
|
79
|
+
- lib/scl/control/output.rb
|
80
|
+
- lib/scl/control/rsa.rb
|
81
|
+
- lib/scl/control/sss.rb
|
82
|
+
- lib/scl/dh.rb
|
83
|
+
- lib/scl/digest.rb
|
84
|
+
- lib/scl/formats/auto.rb
|
85
|
+
- lib/scl/formats/base64.rb
|
86
|
+
- lib/scl/formats/binary.rb
|
87
|
+
- lib/scl/formats/format.rb
|
88
|
+
- lib/scl/formats/hex.rb
|
89
|
+
- lib/scl/formats/qrcode.rb
|
90
|
+
- lib/scl/formats/stdout.rb
|
91
|
+
- lib/scl/formats/wordlist.txt
|
92
|
+
- lib/scl/formats/words.rb
|
93
|
+
- lib/scl/rsa.rb
|
94
|
+
- lib/scl/secret_share.rb
|
95
|
+
- lib/scl/version.rb
|
96
|
+
- scl.gemspec
|
97
|
+
homepage:
|
98
|
+
licenses: []
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.5.1
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Simple crypto library
|
120
|
+
test_files: []
|