├── .gitignore ├── .travis.yml ├── README.md ├── gf256_test.go ├── sss_test.go ├── LICENSE ├── polynomial.go ├── polynomial_test.go ├── sss.go └── gf256.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.test -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.3.3 4 | notifications: 5 | # See http://about.travis-ci.org/docs/user/build-configuration/ to learn more 6 | # about configuring notification recipients and more. 7 | email: 8 | recipients: 9 | - coda.hale@gmail.com 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sss (Shamir's Secret Sharing) 2 | 3 | [![Build Status](https://travis-ci.org/codahale/sss.png?branch=master)](https://travis-ci.org/codahale/sss) 4 | 5 | A pure Go implementation of 6 | [Shamir's Secret Sharing algorithm](http://en.wikipedia.org/wiki/Shamir's_Secret_Sharing) 7 | over GF(2^8). 8 | 9 | Inspired by @hbs's [Python implementation](https://github.com/hbs/PySSSS). 10 | 11 | For documentation, check [godoc](http://godoc.org/github.com/codahale/sss). 12 | -------------------------------------------------------------------------------- /gf256_test.go: -------------------------------------------------------------------------------- 1 | package sss 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMul(t *testing.T) { 8 | if v, want := mul(90, 21), byte(254); v != want { 9 | t.Errorf("Was %v, but expected %v", v, want) 10 | } 11 | } 12 | 13 | func TestDiv(t *testing.T) { 14 | if v, want := div(90, 21), byte(189); v != want { 15 | t.Errorf("Was %v, but expected %v", v, want) 16 | } 17 | } 18 | 19 | func TestDivZero(t *testing.T) { 20 | if v, want := div(0, 2), byte(0); v != want { 21 | t.Errorf("Was %v, but expected %v", v, want) 22 | } 23 | } 24 | 25 | func TestDivByZero(t *testing.T) { 26 | defer func() { 27 | m := recover() 28 | if m != "div by zero" { 29 | t.Error(m) 30 | } 31 | }() 32 | 33 | div(2, 0) 34 | t.Error("Shouldn't have been able to divide those") 35 | } 36 | -------------------------------------------------------------------------------- /sss_test.go: -------------------------------------------------------------------------------- 1 | package sss 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func Example() { 8 | secret := "well hello there!" // our secret 9 | n := byte(30) // create 30 shares 10 | k := byte(2) // require 2 of them to combine 11 | 12 | shares, err := Split(n, k, []byte(secret)) // split into 30 shares 13 | if err != nil { 14 | fmt.Println(err) 15 | return 16 | } 17 | 18 | // select a random subset of the total shares 19 | subset := make(map[byte][]byte, k) 20 | for x, y := range shares { // just iterate since maps are randomized 21 | subset[x] = y 22 | if len(subset) == int(k) { 23 | break 24 | } 25 | } 26 | 27 | // combine two shares and recover the secret 28 | recovered := string(Combine(subset)) 29 | fmt.Println(recovered) 30 | 31 | // Output: well hello there! 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Coda Hale 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /polynomial.go: -------------------------------------------------------------------------------- 1 | package sss 2 | 3 | import "io" 4 | 5 | // the degree of the polynomial 6 | func degree(p []byte) int { 7 | return len(p) - 1 8 | } 9 | 10 | // evaluate the polynomial at the given point 11 | func eval(p []byte, x byte) (result byte) { 12 | // Horner's scheme 13 | for i := 1; i <= len(p); i++ { 14 | result = mul(result, x) ^ p[len(p)-i] 15 | } 16 | return 17 | } 18 | 19 | // generates a random n-degree polynomial w/ a given x-intercept 20 | func generate(degree byte, x byte, rand io.Reader) ([]byte, error) { 21 | result := make([]byte, degree+1) 22 | result[0] = x 23 | 24 | buf := make([]byte, degree-1) 25 | if _, err := io.ReadFull(rand, buf); err != nil { 26 | return nil, err 27 | } 28 | 29 | for i := byte(1); i < degree; i++ { 30 | result[i] = buf[i-1] 31 | } 32 | 33 | // the Nth term can't be zero, or else it's a (N-1) degree polynomial 34 | for { 35 | buf = make([]byte, 1) 36 | if _, err := io.ReadFull(rand, buf); err != nil { 37 | return nil, err 38 | } 39 | 40 | if buf[0] != 0 { 41 | result[degree] = buf[0] 42 | return result, nil 43 | } 44 | } 45 | } 46 | 47 | // an input/output pair 48 | type pair struct { 49 | x, y byte 50 | } 51 | 52 | // Lagrange interpolation 53 | func interpolate(points []pair, x byte) (value byte) { 54 | for i, a := range points { 55 | weight := byte(1) 56 | for j, b := range points { 57 | if i != j { 58 | top := x ^ b.x 59 | bottom := a.x ^ b.x 60 | factor := div(top, bottom) 61 | weight = mul(weight, factor) 62 | } 63 | } 64 | value = value ^ mul(weight, a.y) 65 | } 66 | return 67 | } 68 | -------------------------------------------------------------------------------- /polynomial_test.go: -------------------------------------------------------------------------------- 1 | package sss 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | p = []byte{1, 0, 2, 3} 10 | p2 = []byte{70, 32, 6} 11 | ) 12 | 13 | func TestDegree(t *testing.T) { 14 | if v, want := degree(p), 3; v != want { 15 | t.Errorf("Was %v, but expected %v", v, want) 16 | } 17 | } 18 | 19 | func TestEval(t *testing.T) { 20 | if v, want := eval(p, 2), byte(17); v != want { 21 | t.Errorf("Was %v, but expected %v", v, want) 22 | } 23 | } 24 | 25 | func TestGenerate(t *testing.T) { 26 | b := []byte{1, 2, 3} 27 | 28 | expected := []byte{10, 1, 2, 3} 29 | actual, err := generate(3, 10, bytes.NewReader(b)) 30 | if err != nil { 31 | t.Error(err) 32 | } 33 | 34 | if !bytes.Equal(actual, expected) { 35 | t.Errorf("Was %v, but expected %v", actual, expected) 36 | } 37 | } 38 | 39 | func TestGenerateEOF(t *testing.T) { 40 | b := []byte{1} 41 | 42 | p, err := generate(3, 10, bytes.NewReader(b)) 43 | if p != nil { 44 | t.Errorf("Was %v, but expected an error", p) 45 | } 46 | 47 | if err == nil { 48 | t.Error("No error returned") 49 | } 50 | } 51 | 52 | func TestGeneratePolyEOFFullSize(t *testing.T) { 53 | b := []byte{1, 2, 0, 0, 0, 0} 54 | 55 | p, err := generate(3, 10, bytes.NewReader(b)) 56 | if p != nil { 57 | t.Errorf("Was %v, but xpected an error", p) 58 | } 59 | 60 | if err == nil { 61 | t.Error("No error returned") 62 | } 63 | } 64 | 65 | func TestGenerateFullSize(t *testing.T) { 66 | b := []byte{1, 2, 0, 4} 67 | 68 | expected := []byte{10, 1, 2, 4} 69 | actual, err := generate(3, 10, bytes.NewReader(b)) 70 | if err != nil { 71 | t.Error(err) 72 | } 73 | 74 | if !bytes.Equal(actual, expected) { 75 | t.Errorf("Was %v but expected %v", actual, expected) 76 | } 77 | } 78 | 79 | func TestInterpolate(t *testing.T) { 80 | in := []pair{ 81 | pair{x: 1, y: 1}, 82 | pair{x: 2, y: 2}, 83 | pair{x: 3, y: 3}, 84 | } 85 | 86 | if v, want := interpolate(in, 0), byte(0); v != want { 87 | t.Errorf("Was %v, but expected %v", v, want) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /sss.go: -------------------------------------------------------------------------------- 1 | // Package sss implements Shamir's Secret Sharing algorithm over GF(2^8). 2 | // 3 | // Shamir's Secret Sharing algorithm allows you to securely share a secret with 4 | // N people, allowing the recovery of that secret if K of those people combine 5 | // their shares. 6 | // 7 | // It begins by encoding a secret as a number (e.g., 42), and generating N 8 | // random polynomial equations of degree K-1 which have an X-intercept equal to 9 | // the secret. Given K=3, the following equations might be generated: 10 | // 11 | // f1(x) = 78x^2 + 19x + 42 12 | // f2(x) = 128x^2 + 171x + 42 13 | // f3(x) = 121x^2 + 3x + 42 14 | // f4(x) = 91x^2 + 95x + 42 15 | // etc. 16 | // 17 | // These polynomials are then evaluated for values of X > 0: 18 | // 19 | // f1(1) = 139 20 | // f2(2) = 896 21 | // f3(3) = 1140 22 | // f4(4) = 1783 23 | // etc. 24 | // 25 | // These (x, y) pairs are the shares given to the parties. In order to combine 26 | // shares to recover the secret, these (x, y) pairs are used as the input points 27 | // for Lagrange interpolation, which produces a polynomial which matches the 28 | // given points. This polynomial can be evaluated for f(0), producing the secret 29 | // value--the common x-intercept for all the generated polynomials. 30 | // 31 | // If fewer than K shares are combined, the interpolated polynomial will be 32 | // wrong, and the result of f(0) will not be the secret. 33 | // 34 | // This package constructs polynomials over the field GF(2^8) for each byte of 35 | // the secret, allowing for fast splitting and combining of anything which can 36 | // be encoded as bytes. 37 | // 38 | // This package has not been audited by cryptography or security professionals. 39 | package sss 40 | 41 | import ( 42 | "crypto/rand" 43 | "errors" 44 | ) 45 | 46 | var ( 47 | // ErrInvalidCount is returned when the count parameter is invalid. 48 | ErrInvalidCount = errors.New("N must be >= K") 49 | // ErrInvalidThreshold is returned when the threshold parameter is invalid. 50 | ErrInvalidThreshold = errors.New("K must be > 1") 51 | ) 52 | 53 | // Split the given secret into N shares of which K are required to recover the 54 | // secret. Returns a map of share IDs (1-255) to shares. 55 | func Split(n, k byte, secret []byte) (map[byte][]byte, error) { 56 | if k <= 1 { 57 | return nil, ErrInvalidThreshold 58 | } 59 | 60 | if n < k { 61 | return nil, ErrInvalidCount 62 | } 63 | 64 | shares := make(map[byte][]byte, n) 65 | 66 | for _, b := range secret { 67 | p, err := generate(k-1, b, rand.Reader) 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | for x := byte(1); x <= n; x++ { 73 | shares[x] = append(shares[x], eval(p, x)) 74 | } 75 | } 76 | 77 | return shares, nil 78 | } 79 | 80 | // Combine the given shares into the original secret. 81 | // 82 | // N.B.: There is no way to know whether the returned value is, in fact, the 83 | // original secret. 84 | func Combine(shares map[byte][]byte) []byte { 85 | var secret []byte 86 | for _, v := range shares { 87 | secret = make([]byte, len(v)) 88 | break 89 | } 90 | 91 | points := make([]pair, len(shares)) 92 | for i := range secret { 93 | p := 0 94 | for k, v := range shares { 95 | points[p] = pair{x: k, y: v[i]} 96 | p++ 97 | } 98 | secret[i] = interpolate(points, 0) 99 | } 100 | 101 | return secret 102 | } 103 | -------------------------------------------------------------------------------- /gf256.go: -------------------------------------------------------------------------------- 1 | package sss 2 | 3 | func mul(e, a byte) byte { 4 | if e == 0 || a == 0 { 5 | return 0 6 | } 7 | return exp[(int(log[e])+int(log[a]))%255] 8 | } 9 | 10 | func div(e, a byte) byte { 11 | if a == 0 { 12 | panic("div by zero") 13 | } 14 | 15 | if e == 0 { 16 | return 0 17 | } 18 | 19 | p := (int(log[e]) - int(log[a])) % 255 20 | if p < 0 { 21 | p += 255 22 | } 23 | 24 | return exp[p] 25 | } 26 | 27 | const ( 28 | fieldSize = 256 // 2^8 29 | ) 30 | 31 | var ( 32 | // 0x11b prime polynomial and 0x03 as generator 33 | exp = [fieldSize]byte{ 34 | 0x01, 0x03, 0x05, 0x0f, 0x11, 0x33, 0x55, 0xff, 0x1a, 0x2e, 0x72, 0x96, 35 | 0xa1, 0xf8, 0x13, 0x35, 0x5f, 0xe1, 0x38, 0x48, 0xd8, 0x73, 0x95, 0xa4, 36 | 0xf7, 0x02, 0x06, 0x0a, 0x1e, 0x22, 0x66, 0xaa, 0xe5, 0x34, 0x5c, 0xe4, 37 | 0x37, 0x59, 0xeb, 0x26, 0x6a, 0xbe, 0xd9, 0x70, 0x90, 0xab, 0xe6, 0x31, 38 | 0x53, 0xf5, 0x04, 0x0c, 0x14, 0x3c, 0x44, 0xcc, 0x4f, 0xd1, 0x68, 0xb8, 39 | 0xd3, 0x6e, 0xb2, 0xcd, 0x4c, 0xd4, 0x67, 0xa9, 0xe0, 0x3b, 0x4d, 0xd7, 40 | 0x62, 0xa6, 0xf1, 0x08, 0x18, 0x28, 0x78, 0x88, 0x83, 0x9e, 0xb9, 0xd0, 41 | 0x6b, 0xbd, 0xdc, 0x7f, 0x81, 0x98, 0xb3, 0xce, 0x49, 0xdb, 0x76, 0x9a, 42 | 0xb5, 0xc4, 0x57, 0xf9, 0x10, 0x30, 0x50, 0xf0, 0x0b, 0x1d, 0x27, 0x69, 43 | 0xbb, 0xd6, 0x61, 0xa3, 0xfe, 0x19, 0x2b, 0x7d, 0x87, 0x92, 0xad, 0xec, 44 | 0x2f, 0x71, 0x93, 0xae, 0xe9, 0x20, 0x60, 0xa0, 0xfb, 0x16, 0x3a, 0x4e, 45 | 0xd2, 0x6d, 0xb7, 0xc2, 0x5d, 0xe7, 0x32, 0x56, 0xfa, 0x15, 0x3f, 0x41, 46 | 0xc3, 0x5e, 0xe2, 0x3d, 0x47, 0xc9, 0x40, 0xc0, 0x5b, 0xed, 0x2c, 0x74, 47 | 0x9c, 0xbf, 0xda, 0x75, 0x9f, 0xba, 0xd5, 0x64, 0xac, 0xef, 0x2a, 0x7e, 48 | 0x82, 0x9d, 0xbc, 0xdf, 0x7a, 0x8e, 0x89, 0x80, 0x9b, 0xb6, 0xc1, 0x58, 49 | 0xe8, 0x23, 0x65, 0xaf, 0xea, 0x25, 0x6f, 0xb1, 0xc8, 0x43, 0xc5, 0x54, 50 | 0xfc, 0x1f, 0x21, 0x63, 0xa5, 0xf4, 0x07, 0x09, 0x1b, 0x2d, 0x77, 0x99, 51 | 0xb0, 0xcb, 0x46, 0xca, 0x45, 0xcf, 0x4a, 0xde, 0x79, 0x8b, 0x86, 0x91, 52 | 0xa8, 0xe3, 0x3e, 0x42, 0xc6, 0x51, 0xf3, 0x0e, 0x12, 0x36, 0x5a, 0xee, 53 | 0x29, 0x7b, 0x8d, 0x8c, 0x8f, 0x8a, 0x85, 0x94, 0xa7, 0xf2, 0x0d, 0x17, 54 | 0x39, 0x4b, 0xdd, 0x7c, 0x84, 0x97, 0xa2, 0xfd, 0x1c, 0x24, 0x6c, 0xb4, 55 | 0xc7, 0x52, 0xf6, 0x01, 56 | } 57 | log = [fieldSize]byte{ 58 | 0x00, 0x00, 0x19, 0x01, 0x32, 0x02, 0x1a, 0xc6, 0x4b, 0xc7, 0x1b, 0x68, 59 | 0x33, 0xee, 0xdf, 0x03, 0x64, 0x04, 0xe0, 0x0e, 0x34, 0x8d, 0x81, 0xef, 60 | 0x4c, 0x71, 0x08, 0xc8, 0xf8, 0x69, 0x1c, 0xc1, 0x7d, 0xc2, 0x1d, 0xb5, 61 | 0xf9, 0xb9, 0x27, 0x6a, 0x4d, 0xe4, 0xa6, 0x72, 0x9a, 0xc9, 0x09, 0x78, 62 | 0x65, 0x2f, 0x8a, 0x05, 0x21, 0x0f, 0xe1, 0x24, 0x12, 0xf0, 0x82, 0x45, 63 | 0x35, 0x93, 0xda, 0x8e, 0x96, 0x8f, 0xdb, 0xbd, 0x36, 0xd0, 0xce, 0x94, 64 | 0x13, 0x5c, 0xd2, 0xf1, 0x40, 0x46, 0x83, 0x38, 0x66, 0xdd, 0xfd, 0x30, 65 | 0xbf, 0x06, 0x8b, 0x62, 0xb3, 0x25, 0xe2, 0x98, 0x22, 0x88, 0x91, 0x10, 66 | 0x7e, 0x6e, 0x48, 0xc3, 0xa3, 0xb6, 0x1e, 0x42, 0x3a, 0x6b, 0x28, 0x54, 67 | 0xfa, 0x85, 0x3d, 0xba, 0x2b, 0x79, 0x0a, 0x15, 0x9b, 0x9f, 0x5e, 0xca, 68 | 0x4e, 0xd4, 0xac, 0xe5, 0xf3, 0x73, 0xa7, 0x57, 0xaf, 0x58, 0xa8, 0x50, 69 | 0xf4, 0xea, 0xd6, 0x74, 0x4f, 0xae, 0xe9, 0xd5, 0xe7, 0xe6, 0xad, 0xe8, 70 | 0x2c, 0xd7, 0x75, 0x7a, 0xeb, 0x16, 0x0b, 0xf5, 0x59, 0xcb, 0x5f, 0xb0, 71 | 0x9c, 0xa9, 0x51, 0xa0, 0x7f, 0x0c, 0xf6, 0x6f, 0x17, 0xc4, 0x49, 0xec, 72 | 0xd8, 0x43, 0x1f, 0x2d, 0xa4, 0x76, 0x7b, 0xb7, 0xcc, 0xbb, 0x3e, 0x5a, 73 | 0xfb, 0x60, 0xb1, 0x86, 0x3b, 0x52, 0xa1, 0x6c, 0xaa, 0x55, 0x29, 0x9d, 74 | 0x97, 0xb2, 0x87, 0x90, 0x61, 0xbe, 0xdc, 0xfc, 0xbc, 0x95, 0xcf, 0xcd, 75 | 0x37, 0x3f, 0x5b, 0xd1, 0x53, 0x39, 0x84, 0x3c, 0x41, 0xa2, 0x6d, 0x47, 76 | 0x14, 0x2a, 0x9e, 0x5d, 0x56, 0xf2, 0xd3, 0xab, 0x44, 0x11, 0x92, 0xd9, 77 | 0x23, 0x20, 0x2e, 0x89, 0xb4, 0x7c, 0xb8, 0x26, 0x77, 0x99, 0xe3, 0xa5, 78 | 0x67, 0x4a, 0xed, 0xde, 0xc5, 0x31, 0xfe, 0x18, 0x0d, 0x63, 0x8c, 0x80, 79 | 0xc0, 0xf7, 0x70, 0x07, 80 | } 81 | ) 82 | --------------------------------------------------------------------------------