Skip to content
This repository has been archived by the owner on Aug 28, 2019. It is now read-only.

Commit

Permalink
Major Backwards Incompatible API and Share format changes.
Browse files Browse the repository at this point in the history
This release includes a number of enhancements which
necessitated changes to the public API as well as the String format of
the Share#to_s which is the part that actually gets shared between
people.  The changes include:

- New Share#to_s output which is a Base64 encoded
MessagePack serialization of a JSON Hash of the Share.
This should allow more flexibility since the underlying format
is a Hash that can be used to instantiate a new Share. The
MessagePack and Base64 protocols are platform independent.

- No longer use the pbkdf2 hashes as a way to create a secure Hash
of the original secret.  Instead use a SHA256 HMAC where the HMAC
key is the original secret and the HMAC data is a SHA256 hash of the
Secret.  This allows passing this HMAC as part of the Share String which
in turn allows one to *verify* that the secret that is re-hydrated is
in *fact*
the same as the Secret that was originally used to create the Share.
Previously, one could never be sure that the re-hydrated secret was the
same if for example a typo crept into the Share String.

 - Removed support for the old Share String format.  Use the older
version of the Gem if you need this supported.
  • Loading branch information
grempe committed Dec 24, 2013
1 parent 55fefa3 commit ae20919
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 243 deletions.
78 changes: 42 additions & 36 deletions README.md
Expand Up @@ -10,7 +10,7 @@ k-1 secret share holders learn nothing about the secret when they combine their

Learn More about [Shamir's Secret Sharing](http://en.wikipedia.org/wiki/Shamir's_Secret_Sharing)

### Development History
## Development History

This library is based on the OpenXPKI::Crypto::Secret::Split Perl module
used in the open source PKI software OpenXPKI, which was written by
Expand All @@ -21,10 +21,14 @@ can be found at <http://repo.or.cz/w/secretsharing.git>

It has been further enhanced, modularized, and a full test suite
has been added by Glenn Rempe (<glenn@rempe.us>) and can be found
at <https://github.com/grempe/secretsharing>. The public API of
this new Gem is *not* backwards compatible with 'secretsharing' <= '0.3'.
at <https://github.com/grempe/secretsharing> which is the new canonical
repository for the gem.

## Is it ready?
WARNING : The public API and the Share String format of the current version
of the Gem are *not* backwards compatible with 'secretsharing'
versions <= '0.3'.

## Should I use it?

This code has not yet been tested in production. It is seemingly well tested though with a full Minitest suite and 100% test code coverage and appears to be working well for what it was designed to do. The code also undergoes a continuous integration test run on many different Ruby runtimes upon every push.

Expand Down Expand Up @@ -60,9 +64,6 @@ Or install it yourself as:
# show secret
puts c1.secret

# show password representation of secret (Base64)
puts c1.secret.to_base64

# show shares
c1.shares.each { |share| puts share }

Expand All @@ -78,6 +79,10 @@ Or install it yourself as:

c2.secret? #=> true
puts c2.secret

# Test that the secret used to generate the HMAC
# matches the HMAC of the secret that was re-constructed
c2.secret.valid_hmac? #=> true

## Usage via the command line CLI

Expand All @@ -103,24 +108,28 @@ How many of the total shares (k) are required to reveal the secret? 3
========================================
Encoded Secret:
Base64 URL Safe Secret:
Nm8zajc1MXQ2dmh1aHRranBzdDEzODVjandzMjRqY2RzZGlkMmE1Zjh4ajR4ZXhrMzc=
(k) Value: 3
(n) Value: 5
Secret (Bignum):
121034406494520178855295603459471234790779605059310221238158528187924628493811
179040077567401061920833455639501686558874997550289562553628622313673068089718
Secret (Base64 Compacted & URL Safe):
Nm8zajc1MXQ2dmh1aHRranBzdDEzODVjandzMjRqY2RzZGlkMmE1Zjh4ajR4ZXhrMzc=
OXY1eHdod3N0NXJ1MWEzZXBuMjgxZnN1Y2Y4dXI1bWRyNG40dTl2Zmk1MG16OXM4emE=
Secret has valid_hmac?
true
Shares:
00183DA68F032EFE2C5CE34D789D01C972DF8A20ADEA42D5FF7C783417DA2D8E36441E6B41
0021623A956EB37FBD251D7F6CE412CD20C45C0D8CB2BF66F77F92E6159D6F68B12FD395541
003ABD9F639A3F84C064D5DE55B5E92E5F35DCA42AD6C0EB05336E6D821EC6906BB3387A41
00460B88B5104598F695DD040EFB36DE6BAFDD82CCEAA248A72B385890B19D9E40D1836B41
00580D654B4D4A3874E4ED67FA1115E231B3C374B1679A885DE08C22858F7BB49257840341
2gEqeyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6MSwieSI6OTE5NjU5ODE1Njg0MzAwODU5Mjg2OTU1ODMxMzg0NzA2NDQ1NTMyMzQxNDE5ODAyOTA5NzEwMDcxODU1MTgwMjUyMTYxMjk4Nzg0MzE2fQ==
2gEreyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6MiwieSI6MjI4MDcyMTc2NjA0NjUwODgwODE1ODY0MTc2ODQyOTY5NDkwODgyODY1OTY4Mzg4MzYyODAyNTE0NTI5MzI4NTE1NDI3Njg3NjM0ODkyNn0=
2gEqeyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6MywieSI6NTU2ODc5MDczMDU5OTA2NjU0OTgxNjE5NzQ2NDk2NDU0MDI1MTQzMTkwMjgwNDkxOTQ2NDU4NTExMzAwMjQ4NzY2Nzk5NjUyMzA1NDg5fQ==
2gEreyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6NCwieSI6MzE1ODgyNTQ0NzkxMjczMDkwNjg2NDQzMjgwNjE0MTAwOTg5NzA4NTIxMjIyODIyODg2MTEwODY5NTE2MTQ0NzU5NjE2OTkyMzYxMDEyM30=
2gEreyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6NSwieSI6MjY3NTg2NzE3OTQxNjc0NTA1NjY5ODUzNzkwNjgwNzMzNjQyMjA0NTQ0NjUwODQ5MzM3NTg3NzE3MTU5MTUwNTEzNTk0NzM5MzMwNjcxMH0=
========================================
➜ secretsharing git:(master) ✗
Expand All @@ -141,9 +150,9 @@ Action? 2
How many of shares (k) are required to reveal this secret? 3
Enter the '3' shares one at a time with a RETURN after each:
00183DA68F032EFE2C5CE34D789D01C972DF8A20ADEA42D5FF7C783417DA2D8E36441E6B41
003ABD9F639A3F84C064D5DE55B5E92E5F35DCA42AD6C0EB05336E6D821EC6906BB3387A41
00580D654B4D4A3874E4ED67FA1115E231B3C374B1679A885DE08C22858F7BB49257840341
2gEqeyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6MSwieSI6OTE5NjU5ODE1Njg0MzAwODU5Mjg2OTU1ODMxMzg0NzA2NDQ1NTMyMzQxNDE5ODAyOTA5NzEwMDcxODU1MTgwMjUyMTYxMjk4Nzg0MzE2fQ==
2gEreyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6MiwieSI6MjI4MDcyMTc2NjA0NjUwODgwODE1ODY0MTc2ODQyOTY5NDkwODgyODY1OTY4Mzg4MzYyODAyNTE0NTI5MzI4NTE1NDI3Njg3NjM0ODkyNn0=
2gEqeyJobWFjIjoiMDVkNWNlOTIyNjk5ZTUxNzY4ODU2MmJlYjJiZDUzMTI4OTAyYTYzMjAxMjIxMjdjZTVhZjhlMmRiMmY2MmNkMiIsImsiOjMsIm4iOjUsInByaW1lIjozNzA1MzQ2ODU1NTk0MTE4MjUzNTU0MjcxNTIwMjc4MDEzMDUxMzA0NjM5NTA5MzAwNDk4MDQ5MjYyNjQyNjg4MjUzMjIwMTQ4NDc4MDU5LCJwcmltZV9iaXRsZW5ndGgiOjI2MSwidmVyc2lvbiI6MSwieCI6MywieSI6NTU2ODc5MDczMDU5OTA2NjU0OTgxNjE5NzQ2NDk2NDU0MDI1MTQzMTkwMjgwNDkxOTQ2NDU4NTExMzAwMjQ4NzY2Nzk5NjUyMzA1NDg5fQ==
========================================
Expand All @@ -152,21 +161,18 @@ Decoded Secret:
(k) Value: 3
Secret (Bignum):
121034406494520178855295603459471234790779605059310221238158528187924628493811
179040077567401061920833455639501686558874997550289562553628622313673068089718
Secret (Base64 Compacted & URL Safe):
Nm8zajc1MXQ2dmh1aHRranBzdDEzODVjandzMjRqY2RzZGlkMmE1Zjh4ajR4ZXhrMzc=
OXY1eHdod3N0NXJ1MWEzZXBuMjgxZnN1Y2Y4dXI1bWRyNG40dTl2Zmk1MG16OXM4emE=
========================================
➜ secretsharing git:(master) ✗
````

Easy!

## Caveats & Warnings

* Due to the nature of how Shamir's Secret Sharing works, it cannot tell you if a cheater has given you a Share that was not part of the original set. So if you have 2 real shares, and 1 cheater share of a valid format, the program will still generate and 'decode' a Secret. It just won't be the *right* secret!

## Development and Testing

Install the gemfile dependencies:
Expand All @@ -187,7 +193,7 @@ Build and Install the gem to your local system from the cloned repository:

Run the `secretsharing` binary without installing the Gem locally:

ruby -I./lib bin/secretsharing.rb
ruby -I./lib bin/secretsharing

### Code Quality:

Expand Down Expand Up @@ -248,7 +254,7 @@ This Gem, and its version number, tries its best to adhere to the

### Copyright

(c) 2010-2013 Alexander Klink and Glenn Rempe
(c) 2010-2014 Alexander Klink and Glenn Rempe

### License

Expand All @@ -269,12 +275,12 @@ the License.

## Authors

***Alexander Klink***
<secretsharing@alech.de>
<http://www.alech.de>
@alech on Twitter
***Alexander Klink***</br>
<secretsharing@alech.de></br>
<http://www.alech.de></br>
@alech on Twitter</br>

***Glenn Rempe***
<glenn@rempe.us>
<http://www.rempe.us>
@grempe on Twitter
***Glenn Rempe***</br>
<glenn@rempe.us></br>
<http://www.rempe.us></br>
@grempe on Twitter</br>
6 changes: 4 additions & 2 deletions bin/secretsharing
Expand Up @@ -59,7 +59,6 @@ if choices[:action] == :encode

say("\n========================================\n")
say("Encoded Secret:\n\n")
say("Base64 URL Safe Secret:\n#{@c.secret.to_s}\n\n")
say("(k) Value: #{choices[:secret_k]}\n")
say("(n) Value: #{choices[:secret_n]}\n")
say("\n")
Expand All @@ -69,8 +68,11 @@ if choices[:action] == :encode
say("Secret (Base64 Compacted & URL Safe): \n")
say(@c.secret.to_s)
say("\n")
say("Secret has valid_hmac? \n")
say(@c.secret.valid_hmac?.to_s)
say("\n")
say("Shares:\n")
puts @c.shares
@c.shares.each { |s| say s.to_s }
say("\n========================================\n")

elsif choices[:action] == :decode
Expand Down
2 changes: 1 addition & 1 deletion gemfiles/Gemfile.ci
Expand Up @@ -3,4 +3,4 @@ source "https://rubygems.org"
gem 'rake'
gem 'minitest'
gem 'highline'
gem 'pbkdf2-ruby'
gem 'msgpack'
3 changes: 2 additions & 1 deletion lib/secretsharing.rb
Expand Up @@ -17,7 +17,8 @@
require 'openssl'
require 'digest/sha1'
require 'base64'
require 'pbkdf2'
require 'json'
require 'msgpack'

require 'secretsharing/version'
require 'secretsharing/shamir'
Expand Down
17 changes: 17 additions & 0 deletions lib/secretsharing/shamir.rb
Expand Up @@ -53,5 +53,22 @@ def smallest_prime_of_bitlength(bitlength)

test_prime
end

# Backported for Ruby 1.8.7, REE, JRuby, Rubinious
def urlsafe_decode64(str)
return Base64.urlsafe_decode64(str) if Base64.respond_to?(:urlsafe_decode64)

if str.include?('\n')
fail(ArgumentError, 'invalid base64')
else
Base64.decode64(str)
end
end

# Backported for Ruby 1.8.7, REE, JRuby, Rubinious
def urlsafe_encode64(bin)
return Base64.urlsafe_encode64(bin) if Base64.respond_to?(:urlsafe_encode64)
Base64.encode64(bin).tr("\n", '')
end
end # module Shamir
end # module SecretSharing
2 changes: 1 addition & 1 deletion lib/secretsharing/shamir/container.rb
Expand Up @@ -112,7 +112,7 @@ def create_shares
# a SecretSharing::Shamir::Share object.
def construct_share(x, bitlength)
p_x = evaluate_polynomial_at(x)
SecretSharing::Shamir::Share.new(:x => x, :y => p_x, :prime => @prime, :prime_bitlength => bitlength)
SecretSharing::Shamir::Share.new(:x => x, :y => p_x, :prime => @prime, :prime_bitlength => bitlength, :k => @k, :n => @n, :hmac => @secret.hmac)
end

# Evaluate the polynomial at x.
Expand Down
56 changes: 23 additions & 33 deletions lib/secretsharing/shamir/secret.rb
Expand Up @@ -36,14 +36,11 @@ class Secret

MAX_BITLENGTH = 4096

attr_accessor :secret, :bitlength, :pbkdf2_salt, :pbkdf2_iterations, :pbkdf2_hash, :pbkdf2_hash_function
attr_accessor :secret, :bitlength, :hmac

def initialize(opts = {})
opts = {
:secret => get_random_number(256),
:pbkdf2_salt => OpenSSL::Random.random_bytes(16),
:pbkdf2_iterations => 20_000,
:pbkdf2_hash_function => OpenSSL::Digest::SHA512.new
:secret => get_random_number(256)
}.merge!(opts)

# override with options
Expand All @@ -68,14 +65,20 @@ def initialize(opts = {})
@bitlength = @secret.num_bits
fail ArgumentError, "Secret must have a bitlength less than or equal to #{MAX_BITLENGTH}" if @bitlength > MAX_BITLENGTH

initialize_pbkdf2_hash
generate_hmac
end

# Secrets are equal if the OpenSSL::BN in @secret is the same.
def ==(other)
other == @secret
end

# Set a new secret forces regeneration of the HMAC
def secret=(secret)
@secret = secret
generate_hmac
end

def secret?
@secret.is_a?(OpenSSL::BN)
end
Expand All @@ -88,37 +91,24 @@ def to_s
urlsafe_encode64(@secret.to_i.to_s(36))
end

private

def initialize_pbkdf2_hash
fail ArgumentError, 'pbkdf2_salt must be set' if @pbkdf2_salt.nil?
fail ArgumentError, 'pbkdf2_iterations must be set' if @pbkdf2_iterations.nil?
fail ArgumentError, 'pbkdf2_iterations must be an Integer' unless @pbkdf2_iterations.is_a?(Integer)
fail ArgumentError, 'pbkdf2_hash_function must be set' if @pbkdf2_hash_function.nil?

h = PBKDF2.new(:password => @secret.to_s,
:salt => @pbkdf2_salt,
:iterations => @pbkdf2_iterations,
:hash_function => @pbkdf2_hash_function)
def valid_hmac?
return false if !@secret.is_a?(OpenSSL::BN) || @hmac.nil?

@pbkdf2_hash = h.hex_string if h.is_a?(PBKDF2) && !h.nil?
end
hmac_key = @secret.to_s
hmac_data = OpenSSL::Digest::SHA256.new(@secret.to_s).hexdigest

# Backported for Ruby 1.8.7, REE, JRuby, Rubinious
def urlsafe_decode64(str)
return Base64.urlsafe_decode64(str) if Base64.respond_to?(:urlsafe_decode64)
@hmac == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_key, hmac_data)
end

if str.include?('\n')
fail(ArgumentError, 'invalid base64')
else
Base64.decode64(str)
end
end
private

# Backported for Ruby 1.8.7, REE, JRuby, Rubinious
def urlsafe_encode64(bin)
return Base64.urlsafe_encode64(bin) if Base64.respond_to?(:urlsafe_encode64)
Base64.encode64(bin).tr("\n", '')
# The HMAC uses the raw secret itself as the HMAC key, and the SHA256 of the secret as the data.
# This allows later regeneration of the HMAC to confirm that the restored secret is in fact
# identical to what was originally split into shares.
def generate_hmac
hmac_key = @secret.to_s
hmac_data = OpenSSL::Digest::SHA256.new(@secret.to_s).hexdigest
@hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA256.new, hmac_key, hmac_data)
end
end # class Secret
end # module Shamir
Expand Down

0 comments on commit ae20919

Please sign in to comment.