From 36a913ff1f26f639fb4d703a365d4c01f8ab2dfd Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Wed, 19 Dec 2018 18:22:56 +0000 Subject: [PATCH 01/29] str --- ff1/numeral.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ff1/numeral.go diff --git a/ff1/numeral.go b/ff1/numeral.go new file mode 100644 index 0000000..2bef6ef --- /dev/null +++ b/ff1/numeral.go @@ -0,0 +1,55 @@ +package main + +import ( + "math/big" + "fmt" +) + +func num(s []uint16, radix uint64) (*big.Int, *big.Int, error) { + var big_radix, max, bv, x big.Int + if radix > 65536 { + return nil, nil, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) + } + + maxv := uint16(radix - 1) + big_radix.SetUint64(uint64(radix)) + max.SetInt64(1) + + for i, v := range s { + if v > maxv { + return nil, nil, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, v, maxv) + } + bv.SetUint64(uint64(v)) + x.Mul(&x, &big_radix) + x.Add(&x, &bv) + max.Mul(&max, &big_radix) + } + return &x, &max, nil +} + +func str(x *big.Int, m int, radix uint64) []uint16 { + r := make([]uint16, m) + + var big_radix, mod, v big.Int + v.Set(x) + big_radix.SetUint64(radix) + for i := range r { + v.DivMod(&v, &big_radix, &mod) + r[m-i-1] = uint16(mod.Uint64()) + } + return r +} + +func main() { + + s := []uint16{0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9} + n,max,err := num(s,10) + fmt.Println(n) + fmt.Println(max) + fmt.Println(err) + + v := str(n, len(s), 10) + fmt.Println(v) + fmt.Println(n) +} + From 4fa000e858761b0981daf022d1f66ad9ba48a2b9 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 15:22:21 +0000 Subject: [PATCH 02/29] ff1 encrypt working with numerals --- README.md | 8 +++- codec.go | 79 +++++++++++++++++++++++++++++++++++ codec_test.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++ ff1/ff1.go | 65 +++++++++++++++-------------- ff1/ff1_test.go | 56 ++++++++++++++----------- ff1/numeral.go | 55 ------------------------ numeral.go | 81 ++++++++++++++++++++++++++++++++++++ numeral_test.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 445 insertions(+), 113 deletions(-) create mode 100644 codec.go create mode 100644 codec_test.go delete mode 100644 ff1/numeral.go create mode 100644 numeral.go create mode 100644 numeral_test.go diff --git a/README.md b/README.md index 087d3a4..08421c4 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ package main import ( "encoding/hex" "fmt" + "github.com/capitalone/fpe" "github.com/capitalone/fpe/ff1" ) @@ -52,9 +53,12 @@ func main() { panic(err) } + // The alphabet can contain up 65536 unique characters constructed from a Utf-8 string. + alphabet := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-_=[]{}!$%^&*() \u2318" + // Create a new FF1 cipher "object" - // 10 is the radix/base, and 8 is the tweak length. - FF1, err := ff1.NewCipher(10, 8, key, tweak) + // Alphabet defines the supported character set, and 8 is the tweak length. + FF1, err := ff1.NewCipher(alphabet, 8, key, tweak) if err != nil { panic(err) } diff --git a/codec.go b/codec.go new file mode 100644 index 0000000..b2e177d --- /dev/null +++ b/codec.go @@ -0,0 +1,79 @@ +package fpe + +import ( + "fmt" + "unicode/utf8" +) + +// Codec supports the conversion of an arbitrary alphabet into ordinal +// values from 0 to length of alphabet-1. +// Element 'rtu' (rune-to-uint16) supports the mapping from runes to ordinal values. +// Element 'utr' (uint16-to-rune) supports the mapping from ordinal values to runes. +type Codec struct { + rtu map[rune]uint16 + utr []rune +} + +// NewCodec builds a Codec from the set of unique characters taken from the string s. +// The string contains arbitrary Utf-8 characters. +// It is an error to try to construct a codec from an alphabet with more the 65536 characters. +func NewCodec(s string) (Codec, error) { + var ret Codec + ret.rtu = make(map[rune]uint16) + ret.utr = make([]rune, utf8.RuneCountInString(s)) + + var i uint16 + for _, rv := range s { + // duplicates are tolerated, but ignored. + if _, ok := ret.rtu[rv]; !ok { + ret.utr[i] = rv + ret.rtu[rv] = i + if i == 65535 { + return ret, fmt.Errorf("alphabet must contain fewer than 65536 characters") + } + i++ + } + } + // shrink utr to unique characters + ret.utr = ret.utr[0:i] + return ret, nil +} + +// Radix returns the size of the alphabet supported by the Codec. +func (a *Codec) Radix() int { + return len(a.utr) +} + +// Encode the supplied string as an array of ordinal values giving the +// position of each character in the alphabet. +// It is an error for the supplied string to contain characters than are not +// in the alphabet. +func (a *Codec) Encode(s string) ([]uint16, error) { + ret := make([]uint16, utf8.RuneCountInString(s)) + + var ok bool + i := 0 + for _, rv := range s { + ret[i], ok = a.rtu[rv] + if !ok { + return ret, fmt.Errorf("character at position %d is not in alphabet", i) + } + i++ + } + return ret, nil +} + +// Decode constructs a string from an array of ordinal values where each +// value specifies the position of the character in the alphabet. +// It is an error for the array to contain values outside the boundary of the +// alphabet. +func (a *Codec) Decode(n []uint16) (string, error) { + var ret string + for i, v := range n { + if v < 0 || int(v) > len(a.utr)-1 { + return ret, fmt.Errorf("numeral at position %d out of range: %d not in [0..%d]", i, v, len(a.utr)-1) + } + ret = ret + string(a.utr[v]) + } + return ret, nil +} diff --git a/codec_test.go b/codec_test.go new file mode 100644 index 0000000..acfde8f --- /dev/null +++ b/codec_test.go @@ -0,0 +1,106 @@ +package fpe + +import ( + "fmt" + "reflect" + "testing" +) + +var testCodec = []struct { + alphabet string + radix int + input string + output []uint16 + error bool +}{ + { + "0123456789abcdefghijklmnopqrstuvwxyz ", + 37, + "hello world", + []uint16{17, 14, 21, 21, 24, 36, 32, 24, 27, 21, 13}, + false, + }, + { + "hello world", + 8, + "hello world", + []uint16{0, 1, 2, 2, 3, 4, 5, 3, 6, 2, 7}, + false, + }, + { + "hello world\u2318-", + 10, + "\u2318 - hello world", + []uint16{8, 4, 9, 4, 0, 1, 2, 2, 3, 4, 5, 3, 6, 2, 7}, + false, + }, +} + +func TestCodec(t *testing.T) { + for idx, spec := range testCodec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + al, err := NewCodec(spec.alphabet) + if err != nil { + t.Fatalf("Error making codec: %s", err) + } + if al.Radix() != spec.radix { + t.Fatalf("Incorrect radix %d - expected %d", al.Radix(), spec.radix) + } + + es, err := al.Encode(spec.input) + if err != nil { + t.Fatalf("Unable to encode '%s' using alphabet '%s': %s", spec.input, spec.alphabet, err) + } + + if !reflect.DeepEqual(spec.output, es) { + t.Fatalf("Encode output incorrect: %v", es) + } + + s, err := al.Decode(es) + if err != nil { + t.Fatalf("Unable to decode: %s", err) + } + + if s != spec.input { + t.Fatalf("Decode error: got '%s' expected '%s'", s, spec.input) + } + }) + } +} + +func TestEncoder(t *testing.T) { + tests := []struct { + alphabet string + radix int + input string + }{ + { + "", + 0, + "hello world", + }, + { + "helloworld", + 7, + "hello world", + }, + } + + for idx, spec := range tests { + t.Run(fmt.Sprintf("Sample%d", idx+1), func(t *testing.T) { + al, err := NewCodec(spec.alphabet) + if err != nil { + t.Fatalf("Error making codec: %s", err) + } + if al.Radix() != spec.radix { + t.Fatalf("Incorrect radix %d - expected %d", al.Radix(), spec.radix) + } + + _, err = al.Encode(spec.input) + if err == nil { + t.Fatalf("Encode unexpectly succeeded: input '%s', alphabet '%s'", spec.input, spec.alphabet) + } + }) + } +} diff --git a/ff1/ff1.go b/ff1/ff1.go index c34c9c8..25aa823 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -29,6 +29,8 @@ import ( "math" "math/big" "strings" + "github.com/martin/fpe" + "fmt" ) // Note that this is strictly following the official NIST spec guidelines. In the linked PDF Appendix A (README.md), NIST recommends that radix^minLength >= 1,000,000. If you would like to follow that, change this parameter. @@ -61,6 +63,7 @@ type cbcMode interface { // using a particular key, radix, and tweak type Cipher struct { tweak []byte + codec fpe.Codec radix int minLen uint32 maxLen uint32 @@ -72,7 +75,7 @@ type Cipher struct { // NewCipher initializes a new FF1 Cipher for encryption or decryption use // based on the radix, max tweak length, key and tweak parameters. -func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) { +func NewCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) @@ -82,10 +85,16 @@ func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - // While FF1 allows radices in [2, 2^16], - // realistically there's a practical limit based on the alphabet that can be passed in - if (radix < 2) || (radix > big.MaxBase) { - return newCipher, errors.New("radix must be between 2 and 36, inclusive") + codec, err := fpe.NewCodec(alphabet) + if err != nil { + return newCipher, fmt.Errorf("error making codec: %s", err ) + } + + radix := codec.Radix() + + // FF1 allows radices in [2, 2^16], + if (radix < 2) || (radix > 65536) { + return newCipher, fmt.Errorf("radix must be between 2 and 65536: %d supplied", radix) } // Make sure the length of given tweak is in range @@ -112,7 +121,7 @@ func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) cbcEncryptor := cipher.NewCBCEncrypter(aesBlock, ivZero) newCipher.tweak = tweak - newCipher.radix = radix + newCipher.codec = codec newCipher.minLen = minLen newCipher.maxLen = maxLen newCipher.maxTLen = maxTLen @@ -135,9 +144,16 @@ func (c Cipher) Encrypt(X string) (string, error) { func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { var ret string var err error - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) t := len(tweak) // Check if message length is within minLength and maxLength bounds @@ -150,22 +166,15 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix - - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } + radix := c.codec.Radix() // Calculate split point u := n / 2 v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] // Byte lengths b := int(math.Ceil(math.Ceil(float64(v)*math.Log2(float64(radix))) / 8)) @@ -262,16 +271,17 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - _, ok = numA.SetString(A, radix) - if !ok { + numA, err = fpe.Num(A,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } - _, ok = numB.SetString(B, radix) - if !ok { + numB, err = fpe.Num(B,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } + // Main Feistel Round, 10 times for i := 0; i < numRounds; i++ { // Calculate the dynamic parts of Q @@ -343,16 +353,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numB = numC } - A = numA.Text(radix) - B = numB.Text(radix) - - // Pad both A and B properly - A = strings.Repeat("0", int(u)-len(A)) + A - B = strings.Repeat("0", int(v)-len(B)) + B - - ret = A + B - - return ret, nil + return fpe.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // Decrypt decrypts the string X over the current FF1 parameters diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 90ed8bb..f1563f9 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -29,8 +29,13 @@ import ( // As Golang's sub-tests were introduced in Go 1.7, but this package will work with Go 1.6+, so I'm keeping sub-tests in a separate branch for now. +const ( + alpha_10 = "0123456789" + alpha_36 = "0123456789abcdefghijklmnopqrstuvwxyz" +) + type testVector struct { - radix int + alphabet string // Key and tweak are both hex-encoded strings key string @@ -43,21 +48,21 @@ type testVector struct { var testVectors = []testVector{ // AES-128 { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3C", "", "0123456789", "2433477484", }, { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3C", "39383736353433323130", "0123456789", "6124200773", }, { - 36, + alpha_36, "2B7E151628AED2A6ABF7158809CF4F3C", "3737373770717273373737", "0123456789abcdefghi", @@ -66,21 +71,21 @@ var testVectors = []testVector{ // AES-192 { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "", "0123456789", "2830668132", }, { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "39383736353433323130", "0123456789", "2496655549", }, { - 36, + alpha_36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "3737373770717273373737", "0123456789abcdefghi", @@ -89,21 +94,21 @@ var testVectors = []testVector{ // AES-256 { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "", "0123456789", "6657667009", }, { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "39383736353433323130", "0123456789", "1001623463", }, { - 36, + alpha_36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "3737373770717273373737", "0123456789abcdefghi", @@ -126,7 +131,7 @@ func TestEncrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -137,12 +142,13 @@ func TestEncrypt(t *testing.T) { } if ciphertext != testVector.ciphertext { - t.Fatalf("\nSample%d\nRadix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) + t.Fatalf("\nSample%d\nAlphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) } }) } } +/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -158,7 +164,7 @@ func TestDecrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -169,20 +175,20 @@ func TestDecrypt(t *testing.T) { } if plaintext != testVector.plaintext { - t.Fatalf("\nSample%d\nRadix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) + t.Fatalf("\nSample%d\nalphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) } }) } } -// These are for testing long inputs, which are not in the sandard test vectors +// These are for testing long inputs, which are not in the standard test vectors func TestLong(t *testing.T) { key, err := hex.DecodeString("2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94") tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(36, 16, key, tweak) + ff1, err := NewCipher(alpha_36, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -210,7 +216,7 @@ func TestIssue14(t *testing.T) { tweak, err := hex.DecodeString("D8E7920AFA330A73") - ff1, err := NewCipher(2, 8, key, tweak) + ff1, err := NewCipher("01", 8, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -247,7 +253,7 @@ func ExampleCipher_Encrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(10, 8, key, tweak) + FF1, err := NewCipher(alpha_10, 8, key, tweak) if err != nil { panic(err) } @@ -279,7 +285,7 @@ func ExampleCipher_Decrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(10, 8, key, tweak) + FF1, err := NewCipher(alpha_10, 8, key, tweak) if err != nil { panic(err) } @@ -313,7 +319,7 @@ func BenchmarkNewCipher(b *testing.B) { // 16 is an arbitrary number for maxTlen for n := 0; n < b.N; n++ { - NewCipher(testVector.radix, 16, key, tweak) + NewCipher(testVector.alphabet, 16, key, tweak) } }) } @@ -334,7 +340,7 @@ func BenchmarkEncrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -363,7 +369,7 @@ func BenchmarkDecrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -395,7 +401,7 @@ func BenchmarkE2ESample7(b *testing.B) { for n := 0; n < b.N; n++ { // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -421,7 +427,7 @@ func BenchmarkEncryptLong(b *testing.B) { tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(36, 16, key, tweak) + ff1, err := NewCipher(alpha_36, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -432,3 +438,5 @@ func BenchmarkEncryptLong(b *testing.B) { ff1.Encrypt("xs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwd") } } + +*/ diff --git a/ff1/numeral.go b/ff1/numeral.go deleted file mode 100644 index 2bef6ef..0000000 --- a/ff1/numeral.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "math/big" - "fmt" -) - -func num(s []uint16, radix uint64) (*big.Int, *big.Int, error) { - var big_radix, max, bv, x big.Int - if radix > 65536 { - return nil, nil, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) - } - - maxv := uint16(radix - 1) - big_radix.SetUint64(uint64(radix)) - max.SetInt64(1) - - for i, v := range s { - if v > maxv { - return nil, nil, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, v, maxv) - } - bv.SetUint64(uint64(v)) - x.Mul(&x, &big_radix) - x.Add(&x, &bv) - max.Mul(&max, &big_radix) - } - return &x, &max, nil -} - -func str(x *big.Int, m int, radix uint64) []uint16 { - r := make([]uint16, m) - - var big_radix, mod, v big.Int - v.Set(x) - big_radix.SetUint64(radix) - for i := range r { - v.DivMod(&v, &big_radix, &mod) - r[m-i-1] = uint16(mod.Uint64()) - } - return r -} - -func main() { - - s := []uint16{0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9} - n,max,err := num(s,10) - fmt.Println(n) - fmt.Println(max) - fmt.Println(err) - - v := str(n, len(s), 10) - fmt.Println(v) - fmt.Println(n) -} - diff --git a/numeral.go b/numeral.go new file mode 100644 index 0000000..3c4bb83 --- /dev/null +++ b/numeral.go @@ -0,0 +1,81 @@ +package fpe + +import ( + "fmt" + "math/big" +) + +// Num constructs a big.Int from an array of uint16, where each element represents +// one digit in the given radix. The array is arranged with the most significant digit in element 0, +// down to the least significant digit in element len-1. +func Num(s []uint16, radix uint64) (big.Int, error) { + var big_radix, bv, x big.Int + if radix > 65536 { + return x, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) + } + + maxv := uint16(radix - 1) + big_radix.SetUint64(uint64(radix)) + for i, v := range s { + if v > maxv { + return x, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, v, maxv) + } + bv.SetUint64(uint64(v)) + x.Mul(&x, &big_radix) + x.Add(&x, &bv) + } + return x, nil +} + +// Str populates an array of uint16 with digits representing big.Int x in the specified radix. +// The array is arranged with the most significant digint in element 0. +// The array is built from big.Int x from the least significant digit upwards. If the supplied +// array is too short, the most significant digits of x are quietly lost. +func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { + + var big_radix, mod, v big.Int + if radix > 65536 { + return r, fmt.Errorf("Radix (%d) too big: max supported radix os 65536", radix) + } + m := len(r) + v.Set(x) + big_radix.SetUint64(radix) + for i := range r { + v.DivMod(&v, &big_radix, &mod) + r[m-i-1] = uint16(mod.Uint64()) + } + if v.Sign() != 0 { + return r, fmt.Errorf("destination array too small: %s remains after conversion", &v) + } + return r, nil +} + +// EncodeNum constructs a big Int that represents the ordinal values of string s +// with respect to the alphabet built into the codec. +func EncodeNum(s string, c Codec) (*big.Int, error) { + numeral, err := c.Encode(s) + if err != nil { + return nil, err + } + v, err := Num(numeral,uint64(c.Radix())) + if err != nil { + return nil, err + } + return &v, nil +} + +// DecodeNum constructs a string from the ordinals encoded in two big Ints. +// len_a and len_b are the number of characters that should be built from the corresponding big Ints. +func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,error) { + ret := make([]uint16,len_a+len_b) + _, err := Str(a, ret[:len_a], uint64(c.Radix())) + if err != nil { + return "", err + } + _, err = Str(b, ret[len_a:], uint64(c.Radix())) + if err != nil { + return "", err + } + return c.Decode(ret) +} + diff --git a/numeral_test.go b/numeral_test.go new file mode 100644 index 0000000..3453d0d --- /dev/null +++ b/numeral_test.go @@ -0,0 +1,108 @@ +package fpe + +import ( + "fmt" + "math/big" + "reflect" + "testing" +) + +func TestEncode(t *testing.T) { + + testSpec := []struct { + radix uint64 + intv *big.Int + numeral []uint16 + }{ + { + 10, + big.NewInt(100), + []uint16{1, 0, 0}, + }, + { + 65536, + big.NewInt(0).Exp(big.NewInt(65536), big.NewInt(7), nil), + []uint16{1, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + for idx, spec := range testSpec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + v, err := Num(spec.numeral, spec.radix) + if err != nil { + t.Fatalf("error in Num: %s", err) + } + if v.Cmp(spec.intv) != 0 { + t.Fatalf("expected %v got %v", spec.intv, &v) + } + r := make([]uint16, len(spec.numeral)) + Str(&v, r, spec.radix) + if !reflect.DeepEqual(spec.numeral, r) { + t.Fatalf("Encode numeral incorrect: %v", r) + } + + }) + } +} + +func TestEncodeError(t *testing.T) { + + testSpec := []struct { + radix uint64 + intv *big.Int + numeral []uint16 + }{ + { + 10, + big.NewInt(100), + []uint16{10, 0, 0}, + }, + { + 65537, + big.NewInt(0).Exp(big.NewInt(65537), big.NewInt(7), nil), + []uint16{1, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + for idx, spec := range testSpec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + _, err := Num(spec.numeral, spec.radix) + if err == nil { + t.Fatalf("expected error in Num") + } + }) + } +} + +func TestDecodeError(t *testing.T) { + + testSpec := []struct { + radix uint64 + intv *big.Int + numeral []uint16 + }{ + { + 10, + big.NewInt(100), + []uint16{1, 0, 0}, + }, + { + 65537, + big.NewInt(0).Exp(big.NewInt(65537), big.NewInt(7), nil), + []uint16{1, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + for idx, spec := range testSpec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + r := make([]uint16, 2) + _,err := Str(spec.intv, r, spec.radix) + if err == nil { + t.Fatalf("expected error in Str") + } + }) + } +} From faf9dc2635d061b4abaa5bbf1e2297e04201435d Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 15:36:00 +0000 Subject: [PATCH 03/29] ff1 - decrypt working with numerals --- ff1/ff1.go | 48 +++++++++++++++++++----------------------------- ff1/ff1_test.go | 2 -- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/ff1/ff1.go b/ff1/ff1.go index 25aa823..811cfe7 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -28,7 +28,6 @@ import ( "errors" "math" "math/big" - "strings" "github.com/martin/fpe" "fmt" ) @@ -206,7 +205,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { binary.BigEndian.PutUint32(P[8:12], n) binary.BigEndian.PutUint32(P[12:lenP], uint32(t)) - // Determinte lengths of byte slices + // Determine lengths of byte slices // Q's length is known to always be t+b+1+numPad, to be multiple of 16 lenQ := t + b + 1 + numPad @@ -370,9 +369,16 @@ func (c Cipher) Decrypt(X string) (string, error) { func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { var ret string var err error - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) t := len(tweak) // Check if message length is within minLength and maxLength bounds @@ -385,22 +391,15 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix - - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } + radix := c.codec.Radix() // Calculate split point u := n / 2 v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] // Byte lengths b := int(math.Ceil(math.Ceil(float64(v)*math.Log2(float64(radix))) / 8)) @@ -432,7 +431,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { binary.BigEndian.PutUint32(P[8:12], n) binary.BigEndian.PutUint32(P[12:lenP], uint32(t)) - // Determinte lengths of byte slices + // Determine lengths of byte slices // Q's length is known to always be t+b+1+numPad, to be multiple of 16 lenQ := t + b + 1 + numPad @@ -497,13 +496,13 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - _, ok = numA.SetString(A, radix) - if !ok { + numA, err = fpe.Num(A,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } - _, ok = numB.SetString(B, radix) - if !ok { + numB, err = fpe.Num(B,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -578,16 +577,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numA = numC } - A = numA.Text(radix) - B = numB.Text(radix) - - // Pad both A and B properly - A = strings.Repeat("0", int(u)-len(A)) + A - B = strings.Repeat("0", int(v)-len(B)) + B - - ret = A + B - - return ret, nil + return fpe.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // ciph defines how the main block cipher is called. diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index f1563f9..a3a6d17 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -148,7 +148,6 @@ func TestEncrypt(t *testing.T) { } } -/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -439,4 +438,3 @@ func BenchmarkEncryptLong(b *testing.B) { } } -*/ From d4c568e23a96c8918c87cd9041811cc3aa8e80b8 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 15:49:48 +0000 Subject: [PATCH 04/29] copyright and pruning --- codec.go | 18 ++++++++++++++++++ codec_test.go | 19 +++++++++++++++++++ numeral.go | 38 +++++++++++++++++++++++--------------- numeral_test.go | 18 ++++++++++++++++++ 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/codec.go b/codec.go index b2e177d..da4755e 100644 --- a/codec.go +++ b/codec.go @@ -1,3 +1,21 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ package fpe import ( diff --git a/codec_test.go b/codec_test.go index acfde8f..2a8928d 100644 --- a/codec_test.go +++ b/codec_test.go @@ -1,3 +1,22 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ + package fpe import ( diff --git a/numeral.go b/numeral.go index 3c4bb83..b4a9507 100644 --- a/numeral.go +++ b/numeral.go @@ -1,3 +1,24 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ + +// Package fpe provides some encoding helpers for use +// in the FF1 and FF3 format-preserving encryption packages. package fpe import ( @@ -50,21 +71,8 @@ func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { return r, nil } -// EncodeNum constructs a big Int that represents the ordinal values of string s -// with respect to the alphabet built into the codec. -func EncodeNum(s string, c Codec) (*big.Int, error) { - numeral, err := c.Encode(s) - if err != nil { - return nil, err - } - v, err := Num(numeral,uint64(c.Radix())) - if err != nil { - return nil, err - } - return &v, nil -} - -// DecodeNum constructs a string from the ordinals encoded in two big Ints. +// DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices +// are encoded in the big Ints a and b. // len_a and len_b are the number of characters that should be built from the corresponding big Ints. func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,error) { ret := make([]uint16,len_a+len_b) diff --git a/numeral_test.go b/numeral_test.go index 3453d0d..4a27a27 100644 --- a/numeral_test.go +++ b/numeral_test.go @@ -1,3 +1,21 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ package fpe import ( From dde0e2f823cb6515beb34cab6dd6b57168e8ded1 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 16:38:51 +0000 Subject: [PATCH 05/29] added test for unicode alphabet --- ff1/ff1.go | 37 +++++++++++++++--------- ff1/ff1_test.go | 76 +++++++++++++++++++++++++++++++++---------------- numeral.go | 15 +++++----- numeral_test.go | 4 +-- 4 files changed, 84 insertions(+), 48 deletions(-) diff --git a/ff1/ff1.go b/ff1/ff1.go index 811cfe7..bb2d0b9 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -26,10 +26,10 @@ import ( "crypto/cipher" "encoding/binary" "errors" + "fmt" + "github.com/capitalone/fpe" "math" "math/big" - "github.com/martin/fpe" - "fmt" ) // Note that this is strictly following the official NIST spec guidelines. In the linked PDF Appendix A (README.md), NIST recommends that radix^minLength >= 1,000,000. If you would like to follow that, change this parameter. @@ -72,9 +72,19 @@ type Cipher struct { cbcEncryptor cipher.BlockMode } -// NewCipher initializes a new FF1 Cipher for encryption or decryption use -// based on the radix, max tweak length, key and tweak parameters. -func NewCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { +const ( + // from func (*big.Int)SetString + legacy_alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" +) + +// NewCipher is provided for backwards compatibility for old client code. +func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) { + return NewAlphaCipher(legacy_alphabet[:radix], maxTLen, key, tweak) +} + +// NewAlphaCipher initializes a new FF1 Cipher for encryption or decryption use +// based on the alphabet, max tweak length, key and tweak parameters. +func NewAlphaCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) @@ -86,7 +96,7 @@ func NewCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, codec, err := fpe.NewCodec(alphabet) if err != nil { - return newCipher, fmt.Errorf("error making codec: %s", err ) + return newCipher, fmt.Errorf("error making codec: %s", err) } radix := codec.Radix() @@ -145,13 +155,13 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { var err error // String X contains a sequence of characters, where some characters - // might take up multiple bytes. Convert into an array of indices into + // might take up multiple bytes. Convert into an array of indices into // the alphabet embedded in the codec. Xn, err := c.codec.Encode(X) if err != nil { return ret, ErrStringNotInRadix } - + n := uint32(len(Xn)) t := len(tweak) @@ -270,17 +280,16 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpe.Num(A,uint64(radix)) + numA, err = fpe.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpe.Num(B,uint64(radix)) + numB, err = fpe.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - // Main Feistel Round, 10 times for i := 0; i < numRounds; i++ { // Calculate the dynamic parts of Q @@ -371,7 +380,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { var err error // String X contains a sequence of characters, where some characters - // might take up multiple bytes. Convert into an array of indices into + // might take up multiple bytes. Convert into an array of indices into // the alphabet embedded in the codec. Xn, err := c.codec.Encode(X) if err != nil { @@ -496,12 +505,12 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpe.Num(A,uint64(radix)) + numA, err = fpe.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpe.Num(B,uint64(radix)) + numB, err = fpe.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index a3a6d17..9e04cce 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -35,7 +35,7 @@ const ( ) type testVector struct { - alphabet string + radix int // Key and tweak are both hex-encoded strings key string @@ -48,21 +48,21 @@ type testVector struct { var testVectors = []testVector{ // AES-128 { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3C", "", "0123456789", "2433477484", }, { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3C", "39383736353433323130", "0123456789", "6124200773", }, { - alpha_36, + 36, "2B7E151628AED2A6ABF7158809CF4F3C", "3737373770717273373737", "0123456789abcdefghi", @@ -71,21 +71,21 @@ var testVectors = []testVector{ // AES-192 { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "", "0123456789", "2830668132", }, { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "39383736353433323130", "0123456789", "2496655549", }, { - alpha_36, + 36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "3737373770717273373737", "0123456789abcdefghi", @@ -94,21 +94,21 @@ var testVectors = []testVector{ // AES-256 { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "", "0123456789", "6657667009", }, { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "39383736353433323130", "0123456789", "1001623463", }, { - alpha_36, + 36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "3737373770717273373737", "0123456789abcdefghi", @@ -131,7 +131,7 @@ func TestEncrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -142,7 +142,7 @@ func TestEncrypt(t *testing.T) { } if ciphertext != testVector.ciphertext { - t.Fatalf("\nSample%d\nAlphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) + t.Fatalf("\nSample%d\nradix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) } }) } @@ -163,7 +163,7 @@ func TestDecrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -174,7 +174,7 @@ func TestDecrypt(t *testing.T) { } if plaintext != testVector.plaintext { - t.Fatalf("\nSample%d\nalphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) + t.Fatalf("\nSample%d\nradix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) } }) } @@ -187,7 +187,7 @@ func TestLong(t *testing.T) { tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(alpha_36, 16, key, tweak) + ff1, err := NewCipher(36, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -215,7 +215,7 @@ func TestIssue14(t *testing.T) { tweak, err := hex.DecodeString("D8E7920AFA330A73") - ff1, err := NewCipher("01", 8, key, tweak) + ff1, err := NewCipher(2, 8, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -237,6 +237,35 @@ func TestIssue14(t *testing.T) { } } +// Alphabet can contain unicode characters +func TestUnicode(t *testing.T) { + key, err := hex.DecodeString("EF4359D8D580AA4F7F036D6F04FC6A94") + + tweak, err := hex.DecodeString("D8E7920AFA330A73") + + // 0-9 plus a 1-byte, 2-byte, 3-byte and 4-byte utf-8 chars + ff1, err := NewAlphaCipher("0123456789\u0024\u00A2\u0939\u10348", 8, key, tweak) + if err != nil { + t.Fatalf("Unable to create cipher: %v", err) + } + + plaintext := "0123456789\u0024\u00A2\u0939\u10348" + + ciphertext, err := ff1.Encrypt(plaintext) + if err != nil { + t.Fatalf("%v", err) + } + + decrypted, err := ff1.Decrypt(ciphertext) + if err != nil { + t.Fatalf("%v", err) + } + + if plaintext != decrypted { + t.Fatalf("Issue 14 Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + } +} + // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. @@ -252,7 +281,7 @@ func ExampleCipher_Encrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(alpha_10, 8, key, tweak) + FF1, err := NewCipher(10, 8, key, tweak) if err != nil { panic(err) } @@ -284,7 +313,7 @@ func ExampleCipher_Decrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(alpha_10, 8, key, tweak) + FF1, err := NewCipher(10, 8, key, tweak) if err != nil { panic(err) } @@ -318,7 +347,7 @@ func BenchmarkNewCipher(b *testing.B) { // 16 is an arbitrary number for maxTlen for n := 0; n < b.N; n++ { - NewCipher(testVector.alphabet, 16, key, tweak) + NewCipher(testVector.radix, 16, key, tweak) } }) } @@ -339,7 +368,7 @@ func BenchmarkEncrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -368,7 +397,7 @@ func BenchmarkDecrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -400,7 +429,7 @@ func BenchmarkE2ESample7(b *testing.B) { for n := 0; n < b.N; n++ { // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -426,7 +455,7 @@ func BenchmarkEncryptLong(b *testing.B) { tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(alpha_36, 16, key, tweak) + ff1, err := NewCipher(36, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -437,4 +466,3 @@ func BenchmarkEncryptLong(b *testing.B) { ff1.Encrypt("xs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwd") } } - diff --git a/numeral.go b/numeral.go index b4a9507..75f9b7b 100644 --- a/numeral.go +++ b/numeral.go @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations */ -// Package fpe provides some encoding helpers for use +// Package fpe provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. package fpe @@ -52,7 +52,7 @@ func Num(s []uint16, radix uint64) (big.Int, error) { // The array is arranged with the most significant digint in element 0. // The array is built from big.Int x from the least significant digit upwards. If the supplied // array is too short, the most significant digits of x are quietly lost. -func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { +func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { var big_radix, mod, v big.Int if radix > 65536 { @@ -71,11 +71,11 @@ func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { return r, nil } -// DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices -// are encoded in the big Ints a and b. -// len_a and len_b are the number of characters that should be built from the corresponding big Ints. -func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,error) { - ret := make([]uint16,len_a+len_b) +// DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices +// are encoded in the big Ints a and b. +// len_a and len_b are the number of characters that should be built from the corresponding big Ints. +func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string, error) { + ret := make([]uint16, len_a+len_b) _, err := Str(a, ret[:len_a], uint64(c.Radix())) if err != nil { return "", err @@ -86,4 +86,3 @@ func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,er } return c.Decode(ret) } - diff --git a/numeral_test.go b/numeral_test.go index 4a27a27..12510de 100644 --- a/numeral_test.go +++ b/numeral_test.go @@ -116,8 +116,8 @@ func TestDecodeError(t *testing.T) { for idx, spec := range testSpec { sampleNumber := idx + 1 t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { - r := make([]uint16, 2) - _,err := Str(spec.intv, r, spec.radix) + r := make([]uint16, 2) + _, err := Str(spec.intv, r, spec.radix) if err == nil { t.Fatalf("expected error in Str") } From 3f35a280d0a125c5df10e67cbf623f6787357c8d Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 17:17:52 +0000 Subject: [PATCH 06/29] updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08421c4..a5d9014 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ func main() { // Create a new FF1 cipher "object" // Alphabet defines the supported character set, and 8 is the tweak length. - FF1, err := ff1.NewCipher(alphabet, 8, key, tweak) + FF1, err := ff1.NewAlphaCipher(alphabet, 8, key, tweak) if err != nil { panic(err) } From 3821a5f3d9966d31738043165522063c77ed3413 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 17:22:48 +0000 Subject: [PATCH 07/29] test tidy --- ff1/ff1_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 9e04cce..9c03ba4 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -29,11 +29,6 @@ import ( // As Golang's sub-tests were introduced in Go 1.7, but this package will work with Go 1.6+, so I'm keeping sub-tests in a separate branch for now. -const ( - alpha_10 = "0123456789" - alpha_36 = "0123456789abcdefghijklmnopqrstuvwxyz" -) - type testVector struct { radix int @@ -262,7 +257,7 @@ func TestUnicode(t *testing.T) { } if plaintext != decrypted { - t.Fatalf("Issue 14 Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + t.Fatalf("TestUnicode Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) } } From 7bc711698c6c8dfa67f1d5c620ba60885cc0fddb Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Sat, 22 Dec 2018 10:40:51 +0000 Subject: [PATCH 08/29] FF3 - encrypt --- codec.go | 8 +++- ff3/ff3.go | 99 ++++++++++++++++++++++++++++++++----------------- ff3/ff3_test.go | 2 + numeral.go | 46 ++++++++++++++++++++++- 4 files changed, 118 insertions(+), 37 deletions(-) diff --git a/codec.go b/codec.go index da4755e..1fcb2de 100644 --- a/codec.go +++ b/codec.go @@ -67,7 +67,13 @@ func (a *Codec) Radix() int { // It is an error for the supplied string to contain characters than are not // in the alphabet. func (a *Codec) Encode(s string) ([]uint16, error) { - ret := make([]uint16, utf8.RuneCountInString(s)) + n := utf8.RuneCountInString(s) + c := n + if n%2 == 1 { + // ensure the numeral array has even-sized capacity for FF3 + c++ + } + ret := make([]uint16, n, c) var ok bool i := 0 diff --git a/ff3/ff3.go b/ff3/ff3.go index ad5cbdd..7dd9a1b 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -25,6 +25,8 @@ import ( "crypto/aes" "crypto/cipher" "errors" + "fmt" + "github.com/capitalone/fpe" "math" "math/big" ) @@ -51,7 +53,7 @@ var ( // using a particular key, radix, and tweak type Cipher struct { tweak []byte - radix int + codec fpe.Codec minLen uint32 maxLen uint32 @@ -59,9 +61,19 @@ type Cipher struct { aesBlock cipher.Block } -// NewCipher initializes a new FF3 Cipher for encryption or decryption use -// based on the radix, key and tweak parameters. +const ( + // from func (*big.Int)SetString + legacy_alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" +) + +// NewCipher is provided for backwards compatibility for old client code. func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { + return NewAlphaCipher(legacy_alphabet[:radix], key, tweak) +} + +// NewAlphaCipher initializes a new FF3 Cipher for encryption or decryption use +// based on the alphabet, max tweak length, key and tweak parameters. +func NewAlphaCipher(alphabet string, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) @@ -71,9 +83,16 @@ func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - // While FF3 allows radices in [2, 2^16], there is a practical limit to 36 (alphanumeric) because the Go math/big library only supports up to base 36. - if (radix < 2) || (radix > big.MaxBase) { - return newCipher, errors.New("radix must be between 2 and 36, inclusive") + codec, err := fpe.NewCodec(alphabet) + if err != nil { + return newCipher, fmt.Errorf("error making codec: %s", err) + } + + radix := codec.Radix() + + // FF3 allows radices in [2, 2^16] + if (radix < 2) || (radix > 65536) { + return newCipher, errors.New("radix must be between 2 and 65536, inclusive") } // Make sure the given the length of tweak in bits is 64 @@ -99,7 +118,7 @@ func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { } newCipher.tweak = tweak - newCipher.radix = radix + newCipher.codec = codec newCipher.minLen = minLen newCipher.maxLen = maxLen newCipher.aesBlock = aesBlock @@ -120,9 +139,16 @@ func (c Cipher) Encrypt(X string) (string, error) { // use-case of FPE for things like credit card numbers. func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { var ret string - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) // Check if message length is within minLength and maxLength bounds // TODO BUG: when n==c.maxLen, it breaks. For now, I'm changing @@ -136,22 +162,18 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix - - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } + radix := c.codec.Radix() // Calculate split point u := uint32(math.Ceil(float64(n) / 2)) v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] + + // C must be large enough to hold either A or B + C := make([]uint16, u) // Split the tweak Tl := tweak[:halfTweakLen] @@ -200,8 +222,8 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(B) with padding - _, ok = numB.SetString(rev(B), radix) - if !ok { + numB, err = fpe.NumRev(B, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -225,8 +247,8 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numY.SetBytes(S[:]) // Calculate c - _, ok = numC.SetString(rev(A), radix) - if !ok { + numC, err = fpe.NumRev(A, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -238,22 +260,29 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numC.Mod(&numC, &numModV) } - C := numC.Text(c.radix) - - // Need to pad the text with leading 0s first to make sure it's the correct length - for len(C) < int(m) { - C = "0" + C + C = C[:m] + _, err := fpe.StrRev(&numC, C, uint64(c.codec.Radix())) + if err != nil { + return "", err } - C = rev(C) // Final steps - A = B - B = C + A, B, C = B, C, A + } + + // convert the numeral arrays back to strings + strA, err := c.codec.Decode(A) + if err != nil { + return "", err + } + + strB, err := c.codec.Decode(B) + if err != nil { + return "", err } - ret = A + B + return strA + strB, nil - return ret, nil } // Decrypt decrypts the string X over the current FF3 parameters @@ -285,7 +314,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix + radix := c.codec.Radix() // Check if the message is in the current radix var numX big.Int @@ -390,7 +419,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numC.Mod(&numC, &numModV) } - C := numC.Text(c.radix) + C := numC.Text(c.codec.Radix()) // Need to pad the text with leading 0s first to make sure it's the correct length for len(C) < int(m) { diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index ea32b33..0f687b3 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -173,6 +173,7 @@ func TestEncrypt(t *testing.T) { } } +/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -322,3 +323,4 @@ func BenchmarkDecrypt(b *testing.B) { }) } } +*/ diff --git a/numeral.go b/numeral.go index 75f9b7b..2da9f65 100644 --- a/numeral.go +++ b/numeral.go @@ -48,8 +48,30 @@ func Num(s []uint16, radix uint64) (big.Int, error) { return x, nil } +// NumRev constructs a big.Int from an array of uint16, where each element represents +// one digit in the given radix. The array is arranged with the least significant digit in element 0, +// down to the most significant digit in element len-1. +func NumRev(s []uint16, radix uint64) (big.Int, error) { + var big_radix, bv, x big.Int + if radix > 65536 { + return x, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) + } + + maxv := uint16(radix - 1) + big_radix.SetUint64(uint64(radix)) + for i := len(s) - 1; i >= 0; i-- { + if s[i] > maxv { + return x, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, s[i], maxv) + } + bv.SetUint64(uint64(s[i])) + x.Mul(&x, &big_radix) + x.Add(&x, &bv) + } + return x, nil +} + // Str populates an array of uint16 with digits representing big.Int x in the specified radix. -// The array is arranged with the most significant digint in element 0. +// The array is arranged with the most significant digit in element 0. // The array is built from big.Int x from the least significant digit upwards. If the supplied // array is too short, the most significant digits of x are quietly lost. func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { @@ -71,6 +93,28 @@ func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { return r, nil } +// StrRev populates an array of uint16 with digits representing big.Int x in the specified radix. +// The array is arranged with the least significant digit in element 0. +// The array is built from big.Int x from the least significant digit upwards. If the supplied +// array is too short, the most significant digits of x are quietly lost. +func StrRev(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { + + var big_radix, mod, v big.Int + if radix > 65536 { + return r, fmt.Errorf("Radix (%d) too big: max supported radix os 65536", radix) + } + v.Set(x) + big_radix.SetUint64(radix) + for i := range r { + v.DivMod(&v, &big_radix, &mod) + r[i] = uint16(mod.Uint64()) + } + if v.Sign() != 0 { + return r, fmt.Errorf("destination array too small: %s remains after conversion", &v) + } + return r, nil +} + // DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices // are encoded in the big Ints a and b. // len_a and len_b are the number of characters that should be built from the corresponding big Ints. From 501132ba46313d02e3b63dd15c6317378d6f5d4d Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Sat, 22 Dec 2018 10:53:57 +0000 Subject: [PATCH 09/29] FF3 - decrypt --- ff3/ff3.go | 62 ++++++++++++++++++++++++++++--------------------- ff3/ff3_test.go | 2 +- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/ff3/ff3.go b/ff3/ff3.go index 7dd9a1b..57a1287 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -298,9 +298,16 @@ func (c Cipher) Decrypt(X string) (string, error) { // use-case of FPE for things like credit card numbers. func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { var ret string - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) // Check if message length is within minLength and maxLength bounds // TODO BUG: when n==c.maxLen, it breaks. For now, I'm changing @@ -316,20 +323,16 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { radix := c.codec.Radix() - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } - // Calculate split point u := uint32(math.Ceil(float64(n) / 2)) v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] + + // C must be large enough to hold either A or B + C := make([]uint16, u) // Split the tweak Tl := tweak[:halfTweakLen] @@ -378,8 +381,8 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(A) with padding - _, ok = numA.SetString(rev(A), radix) - if !ok { + numA, err = fpe.NumRev(A, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -402,12 +405,9 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { // Calculate numY numY.SetBytes(S[:]) - // Calculate numY - numY.SetBytes(S[:]) - // Calculate c - _, ok = numC.SetString(rev(B), radix) - if !ok { + numC, err = fpe.NumRev(B, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -419,20 +419,28 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numC.Mod(&numC, &numModV) } - C := numC.Text(c.codec.Radix()) - - // Need to pad the text with leading 0s first to make sure it's the correct length - for len(C) < int(m) { - C = "0" + C + C = C[:m] + _, err := fpe.StrRev(&numC, C, uint64(c.codec.Radix())) + if err != nil { + return "", err } - C = rev(C) // Final steps - B = A - A = C + B, A, C = A, C, B } - return A + B, nil + // convert the numeral arrays back to strings + strA, err := c.codec.Decode(A) + if err != nil { + return "", err + } + + strB, err := c.codec.Decode(B) + if err != nil { + return "", err + } + + return strA + strB, nil } // rev reverses a string diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index 0f687b3..a54f464 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -173,7 +173,6 @@ func TestEncrypt(t *testing.T) { } } -/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -205,6 +204,7 @@ func TestDecrypt(t *testing.T) { } } +/* // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. From d0357d4c1e619dce5f030ece819f7231bbb07d76 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Sat, 22 Dec 2018 10:55:04 +0000 Subject: [PATCH 10/29] FF3 -reinstate tests --- ff3/ff3_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index a54f464..ea32b33 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -204,7 +204,6 @@ func TestDecrypt(t *testing.T) { } } -/* // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. @@ -323,4 +322,3 @@ func BenchmarkDecrypt(b *testing.B) { }) } } -*/ From 26aad4fcd6b8397cbfd464212aee72dec4452336 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 24 Dec 2018 08:32:20 +0000 Subject: [PATCH 11/29] test numeral bit lengths --- codec.go | 8 +++--- codec_test.go | 38 ++++++++++++++++++++++++++++- ff1/ff1.go | 4 +-- ff1/ff1_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/codec.go b/codec.go index 1fcb2de..ed83e12 100644 --- a/codec.go +++ b/codec.go @@ -40,14 +40,14 @@ func NewCodec(s string) (Codec, error) { ret.rtu = make(map[rune]uint16) ret.utr = make([]rune, utf8.RuneCountInString(s)) - var i uint16 + var i uint32 for _, rv := range s { // duplicates are tolerated, but ignored. if _, ok := ret.rtu[rv]; !ok { ret.utr[i] = rv - ret.rtu[rv] = i - if i == 65535 { - return ret, fmt.Errorf("alphabet must contain fewer than 65536 characters") + ret.rtu[rv] = uint16(i) + if i == 65536 { + return ret, fmt.Errorf("alphabet must contain no more than 65536 characters") } i++ } diff --git a/codec_test.go b/codec_test.go index 2a8928d..a670660 100644 --- a/codec_test.go +++ b/codec_test.go @@ -20,9 +20,11 @@ See the License for the specific language governing permissions and limitations package fpe import ( + "bytes" "fmt" "reflect" "testing" + "unicode/utf8" ) var testCodec = []struct { @@ -118,8 +120,42 @@ func TestEncoder(t *testing.T) { _, err = al.Encode(spec.input) if err == nil { - t.Fatalf("Encode unexpectly succeeded: input '%s', alphabet '%s'", spec.input, spec.alphabet) + t.Fatalf("Encode unexpectedly succeeded: input '%s', alphabet '%s'", spec.input, spec.alphabet) } }) } } + +func TestLargeAlphabet(t *testing.T) { + var alphabet bytes.Buffer + + nr := 0 + for i := 0; i < 100000; i++ { + if utf8.ValidRune(rune(i)) { + s := string(rune(i)) + nr++ + alphabet.WriteString(s) + if nr == 65536 { + break + } + } + } + + al, err := NewCodec(alphabet.String()) + if err != nil { + t.Fatalf("Error making codec: %s", err) + } + if al.Radix() != 65536 { + t.Fatalf("Incorrect radix %d ", al.Radix()) + } + + nml, err := al.Encode("hello world") + if err != nil { + t.Fatalf("Unable to encode: %s", err) + } + + _, err = al.Decode(nml) + if err != nil { + t.Fatalf("Unable to decode: %s", err) + } +} diff --git a/ff1/ff1.go b/ff1/ff1.go index bb2d0b9..4515bf4 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -116,8 +116,8 @@ func NewAlphaCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cip var maxLen uint32 = math.MaxUint32 - // Make sure 2 <= minLength <= maxLength < 2^32 is satisfied - if (minLen < 2) || (maxLen < minLen) || (maxLen > math.MaxUint32) { + // Make sure minLength <= maxLength < 2^32 is satisfied + if (maxLen < minLen) || (maxLen > math.MaxUint32) { return newCipher, errors.New("minLen invalid, adjust your radix") } diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 9c03ba4..7206a36 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -20,9 +20,12 @@ See the License for the specific language governing permissions and limitations package ff1 import ( + "bytes" "encoding/hex" "fmt" "testing" + "unicode/utf8" + "strings" ) // Test vectors taken from here: http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/FF1samples.pdf @@ -261,6 +264,68 @@ func TestUnicode(t *testing.T) { } } +func TestAlphabetSizes(t *testing.T) { + // encryption deals with numeral values encoded in ceil(log(radix))-sized + // bit strings, up to 16 bits in length - the number of bits in a uint16. + // This test exercises behaviour for all bitstring lengths from 1 to 16. + + key, _ := hex.DecodeString("EF4359D8D580AA4F7F036D6F04FC6A94") + + tweak, _ := hex.DecodeString("D8E7920AFA330A73") + + for s := uint(1); s < 17; s++ { + a, err := buildAlphabet(1 << s) + if err != nil { + t.Fatalf("TestAlphabetSizes: %s", err) + } + + ff1, err := NewAlphaCipher(a, 8, key, tweak) + if err != nil { + t.Fatalf("Unable to create cipher: %v", err) + } + + plaintext := strings.Repeat(string(rune(0)),10) + ciphertext, err := ff1.Encrypt(plaintext) + if err != nil { + t.Fatalf("%v", err) + } + + decrypted, err := ff1.Decrypt(ciphertext) + if err != nil { + t.Fatalf("%v", err) + } + + if plaintext != decrypted { + t.Fatalf("TestUnicode Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + } + + } + +} + +func buildAlphabet(n int) (string, error) { + // Not every code-point can be encoded as utf-8 string. + // For example u+DC00 - u+DFFF contains "isolated surrogate code points" + // that have no string interpretation. + // (https://www.unicode.org/charts/PDF/UDC00.pdf) + // + // Loop through a large number of code points and collect + // up to n code points with valid interpretations. + var alphabet bytes.Buffer + nr := 0 + for i := 0; i < 100000; i++ { + if utf8.ValidRune(rune(i)) { + s := string(rune(i)) + nr++ + alphabet.WriteString(s) + if nr == n { + return alphabet.String(), nil + } + } + } + return alphabet.String(), fmt.Errorf("Failed to collect %d validrunes: only %d collected", n, nr) +} + // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. From ef613c9eab523c5b2ec05c018bada4792ad085e0 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 24 Dec 2018 08:39:56 +0000 Subject: [PATCH 12/29] ff3 - test numeral bit lengths --- ff1/ff1_test.go | 4 +-- ff3/ff3.go | 4 +-- ff3/ff3_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 7206a36..05b2601 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -23,9 +23,9 @@ import ( "bytes" "encoding/hex" "fmt" + "strings" "testing" "unicode/utf8" - "strings" ) // Test vectors taken from here: http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/FF1samples.pdf @@ -284,7 +284,7 @@ func TestAlphabetSizes(t *testing.T) { t.Fatalf("Unable to create cipher: %v", err) } - plaintext := strings.Repeat(string(rune(0)),10) + plaintext := strings.Repeat(string(rune(0)), 10) ciphertext, err := ff1.Encrypt(plaintext) if err != nil { t.Fatalf("%v", err) diff --git a/ff3/ff3.go b/ff3/ff3.go index 57a1287..4320ab2 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -105,8 +105,8 @@ func NewAlphaCipher(alphabet string, key []byte, tweak []byte) (Cipher, error) { maxLen := uint32(math.Floor((192 / math.Log2(float64(radix))))) - // Make sure 2 <= minLength <= maxLength < 2*floor(log base radix of 2^96) is satisfied - if (minLen < 2) || (maxLen < minLen) || (float64(maxLen) > (192 / math.Log2(float64(radix)))) { + // Make sure minLength <= maxLength < 2*floor(log base radix of 2^96) is satisfied + if (maxLen < minLen) || (float64(maxLen) > (192 / math.Log2(float64(radix)))) { return newCipher, errors.New("minLen or maxLen invalid, adjust your radix") } diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index ea32b33..1de2d33 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -20,9 +20,12 @@ See the License for the specific language governing permissions and limitations package ff3 import ( + "bytes" "encoding/hex" "fmt" + "strings" "testing" + "unicode/utf8" ) // Test vectors taken from here: http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/FF3samples.pdf @@ -204,6 +207,68 @@ func TestDecrypt(t *testing.T) { } } +func TestAlphabetSizes(t *testing.T) { + // encryption deals with numeral values encoded in ceil(log(radix))-sized + // bit strings, up to 16 bits in length - the number of bits in a uint16. + // This test exercises behaviour for all bitstring lengths from 1 to 16. + + key, _ := hex.DecodeString("EF4359D8D580AA4F7F036D6F04FC6A94") + + tweak, _ := hex.DecodeString("D8E7920AFA330A73") + + for s := uint(1); s < 17; s++ { + a, err := buildAlphabet(1 << s) + if err != nil { + t.Fatalf("TestAlphabetSizes: %s", err) + } + + ff3, err := NewAlphaCipher(a, key, tweak) + if err != nil { + t.Fatalf("Unable to create cipher: %v", err) + } + + plaintext := strings.Repeat(string(rune(0)), 10) + ciphertext, err := ff3.Encrypt(plaintext) + if err != nil { + t.Fatalf("%v", err) + } + + decrypted, err := ff3.Decrypt(ciphertext) + if err != nil { + t.Fatalf("%v", err) + } + + if plaintext != decrypted { + t.Fatalf("TestUnicode Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + } + + } + +} + +func buildAlphabet(n int) (string, error) { + // Not every code-point can be encoded as utf-8 string. + // For example u+DC00 - u+DFFF contains "isolated surrogate code points" + // that have no string interpretation. + // (https://www.unicode.org/charts/PDF/UDC00.pdf) + // + // Loop through a large number of code points and collect + // up to n code points with valid interpretations. + var alphabet bytes.Buffer + nr := 0 + for i := 0; i < 100000; i++ { + if utf8.ValidRune(rune(i)) { + s := string(rune(i)) + nr++ + alphabet.WriteString(s) + if nr == n { + return alphabet.String(), nil + } + } + } + return alphabet.String(), fmt.Errorf("Failed to collect %d validrunes: only %d collected", n, nr) +} + // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. From 4ac1663d3afc997e45dcb25046f2d1a4d4ec468e Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Wed, 19 Dec 2018 18:22:56 +0000 Subject: [PATCH 13/29] str --- ff1/numeral.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 ff1/numeral.go diff --git a/ff1/numeral.go b/ff1/numeral.go new file mode 100644 index 0000000..2bef6ef --- /dev/null +++ b/ff1/numeral.go @@ -0,0 +1,55 @@ +package main + +import ( + "math/big" + "fmt" +) + +func num(s []uint16, radix uint64) (*big.Int, *big.Int, error) { + var big_radix, max, bv, x big.Int + if radix > 65536 { + return nil, nil, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) + } + + maxv := uint16(radix - 1) + big_radix.SetUint64(uint64(radix)) + max.SetInt64(1) + + for i, v := range s { + if v > maxv { + return nil, nil, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, v, maxv) + } + bv.SetUint64(uint64(v)) + x.Mul(&x, &big_radix) + x.Add(&x, &bv) + max.Mul(&max, &big_radix) + } + return &x, &max, nil +} + +func str(x *big.Int, m int, radix uint64) []uint16 { + r := make([]uint16, m) + + var big_radix, mod, v big.Int + v.Set(x) + big_radix.SetUint64(radix) + for i := range r { + v.DivMod(&v, &big_radix, &mod) + r[m-i-1] = uint16(mod.Uint64()) + } + return r +} + +func main() { + + s := []uint16{0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9} + n,max,err := num(s,10) + fmt.Println(n) + fmt.Println(max) + fmt.Println(err) + + v := str(n, len(s), 10) + fmt.Println(v) + fmt.Println(n) +} + From b0559c098d29a4de11d3e1542937430c5a150f11 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 15:22:21 +0000 Subject: [PATCH 14/29] ff1 encrypt working with numerals --- README.md | 8 +++- codec.go | 79 +++++++++++++++++++++++++++++++++++ codec_test.go | 106 +++++++++++++++++++++++++++++++++++++++++++++++ ff1/ff1.go | 65 +++++++++++++++-------------- ff1/ff1_test.go | 56 ++++++++++++++----------- ff1/numeral.go | 55 ------------------------ numeral.go | 81 ++++++++++++++++++++++++++++++++++++ numeral_test.go | 108 ++++++++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 445 insertions(+), 113 deletions(-) create mode 100644 codec.go create mode 100644 codec_test.go delete mode 100644 ff1/numeral.go create mode 100644 numeral.go create mode 100644 numeral_test.go diff --git a/README.md b/README.md index 087d3a4..08421c4 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ package main import ( "encoding/hex" "fmt" + "github.com/capitalone/fpe" "github.com/capitalone/fpe/ff1" ) @@ -52,9 +53,12 @@ func main() { panic(err) } + // The alphabet can contain up 65536 unique characters constructed from a Utf-8 string. + alphabet := "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+-_=[]{}!$%^&*() \u2318" + // Create a new FF1 cipher "object" - // 10 is the radix/base, and 8 is the tweak length. - FF1, err := ff1.NewCipher(10, 8, key, tweak) + // Alphabet defines the supported character set, and 8 is the tweak length. + FF1, err := ff1.NewCipher(alphabet, 8, key, tweak) if err != nil { panic(err) } diff --git a/codec.go b/codec.go new file mode 100644 index 0000000..b2e177d --- /dev/null +++ b/codec.go @@ -0,0 +1,79 @@ +package fpe + +import ( + "fmt" + "unicode/utf8" +) + +// Codec supports the conversion of an arbitrary alphabet into ordinal +// values from 0 to length of alphabet-1. +// Element 'rtu' (rune-to-uint16) supports the mapping from runes to ordinal values. +// Element 'utr' (uint16-to-rune) supports the mapping from ordinal values to runes. +type Codec struct { + rtu map[rune]uint16 + utr []rune +} + +// NewCodec builds a Codec from the set of unique characters taken from the string s. +// The string contains arbitrary Utf-8 characters. +// It is an error to try to construct a codec from an alphabet with more the 65536 characters. +func NewCodec(s string) (Codec, error) { + var ret Codec + ret.rtu = make(map[rune]uint16) + ret.utr = make([]rune, utf8.RuneCountInString(s)) + + var i uint16 + for _, rv := range s { + // duplicates are tolerated, but ignored. + if _, ok := ret.rtu[rv]; !ok { + ret.utr[i] = rv + ret.rtu[rv] = i + if i == 65535 { + return ret, fmt.Errorf("alphabet must contain fewer than 65536 characters") + } + i++ + } + } + // shrink utr to unique characters + ret.utr = ret.utr[0:i] + return ret, nil +} + +// Radix returns the size of the alphabet supported by the Codec. +func (a *Codec) Radix() int { + return len(a.utr) +} + +// Encode the supplied string as an array of ordinal values giving the +// position of each character in the alphabet. +// It is an error for the supplied string to contain characters than are not +// in the alphabet. +func (a *Codec) Encode(s string) ([]uint16, error) { + ret := make([]uint16, utf8.RuneCountInString(s)) + + var ok bool + i := 0 + for _, rv := range s { + ret[i], ok = a.rtu[rv] + if !ok { + return ret, fmt.Errorf("character at position %d is not in alphabet", i) + } + i++ + } + return ret, nil +} + +// Decode constructs a string from an array of ordinal values where each +// value specifies the position of the character in the alphabet. +// It is an error for the array to contain values outside the boundary of the +// alphabet. +func (a *Codec) Decode(n []uint16) (string, error) { + var ret string + for i, v := range n { + if v < 0 || int(v) > len(a.utr)-1 { + return ret, fmt.Errorf("numeral at position %d out of range: %d not in [0..%d]", i, v, len(a.utr)-1) + } + ret = ret + string(a.utr[v]) + } + return ret, nil +} diff --git a/codec_test.go b/codec_test.go new file mode 100644 index 0000000..acfde8f --- /dev/null +++ b/codec_test.go @@ -0,0 +1,106 @@ +package fpe + +import ( + "fmt" + "reflect" + "testing" +) + +var testCodec = []struct { + alphabet string + radix int + input string + output []uint16 + error bool +}{ + { + "0123456789abcdefghijklmnopqrstuvwxyz ", + 37, + "hello world", + []uint16{17, 14, 21, 21, 24, 36, 32, 24, 27, 21, 13}, + false, + }, + { + "hello world", + 8, + "hello world", + []uint16{0, 1, 2, 2, 3, 4, 5, 3, 6, 2, 7}, + false, + }, + { + "hello world\u2318-", + 10, + "\u2318 - hello world", + []uint16{8, 4, 9, 4, 0, 1, 2, 2, 3, 4, 5, 3, 6, 2, 7}, + false, + }, +} + +func TestCodec(t *testing.T) { + for idx, spec := range testCodec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + al, err := NewCodec(spec.alphabet) + if err != nil { + t.Fatalf("Error making codec: %s", err) + } + if al.Radix() != spec.radix { + t.Fatalf("Incorrect radix %d - expected %d", al.Radix(), spec.radix) + } + + es, err := al.Encode(spec.input) + if err != nil { + t.Fatalf("Unable to encode '%s' using alphabet '%s': %s", spec.input, spec.alphabet, err) + } + + if !reflect.DeepEqual(spec.output, es) { + t.Fatalf("Encode output incorrect: %v", es) + } + + s, err := al.Decode(es) + if err != nil { + t.Fatalf("Unable to decode: %s", err) + } + + if s != spec.input { + t.Fatalf("Decode error: got '%s' expected '%s'", s, spec.input) + } + }) + } +} + +func TestEncoder(t *testing.T) { + tests := []struct { + alphabet string + radix int + input string + }{ + { + "", + 0, + "hello world", + }, + { + "helloworld", + 7, + "hello world", + }, + } + + for idx, spec := range tests { + t.Run(fmt.Sprintf("Sample%d", idx+1), func(t *testing.T) { + al, err := NewCodec(spec.alphabet) + if err != nil { + t.Fatalf("Error making codec: %s", err) + } + if al.Radix() != spec.radix { + t.Fatalf("Incorrect radix %d - expected %d", al.Radix(), spec.radix) + } + + _, err = al.Encode(spec.input) + if err == nil { + t.Fatalf("Encode unexpectly succeeded: input '%s', alphabet '%s'", spec.input, spec.alphabet) + } + }) + } +} diff --git a/ff1/ff1.go b/ff1/ff1.go index c34c9c8..25aa823 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -29,6 +29,8 @@ import ( "math" "math/big" "strings" + "github.com/martin/fpe" + "fmt" ) // Note that this is strictly following the official NIST spec guidelines. In the linked PDF Appendix A (README.md), NIST recommends that radix^minLength >= 1,000,000. If you would like to follow that, change this parameter. @@ -61,6 +63,7 @@ type cbcMode interface { // using a particular key, radix, and tweak type Cipher struct { tweak []byte + codec fpe.Codec radix int minLen uint32 maxLen uint32 @@ -72,7 +75,7 @@ type Cipher struct { // NewCipher initializes a new FF1 Cipher for encryption or decryption use // based on the radix, max tweak length, key and tweak parameters. -func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) { +func NewCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) @@ -82,10 +85,16 @@ func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - // While FF1 allows radices in [2, 2^16], - // realistically there's a practical limit based on the alphabet that can be passed in - if (radix < 2) || (radix > big.MaxBase) { - return newCipher, errors.New("radix must be between 2 and 36, inclusive") + codec, err := fpe.NewCodec(alphabet) + if err != nil { + return newCipher, fmt.Errorf("error making codec: %s", err ) + } + + radix := codec.Radix() + + // FF1 allows radices in [2, 2^16], + if (radix < 2) || (radix > 65536) { + return newCipher, fmt.Errorf("radix must be between 2 and 65536: %d supplied", radix) } // Make sure the length of given tweak is in range @@ -112,7 +121,7 @@ func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) cbcEncryptor := cipher.NewCBCEncrypter(aesBlock, ivZero) newCipher.tweak = tweak - newCipher.radix = radix + newCipher.codec = codec newCipher.minLen = minLen newCipher.maxLen = maxLen newCipher.maxTLen = maxTLen @@ -135,9 +144,16 @@ func (c Cipher) Encrypt(X string) (string, error) { func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { var ret string var err error - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) t := len(tweak) // Check if message length is within minLength and maxLength bounds @@ -150,22 +166,15 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix - - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } + radix := c.codec.Radix() // Calculate split point u := n / 2 v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] // Byte lengths b := int(math.Ceil(math.Ceil(float64(v)*math.Log2(float64(radix))) / 8)) @@ -262,16 +271,17 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - _, ok = numA.SetString(A, radix) - if !ok { + numA, err = fpe.Num(A,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } - _, ok = numB.SetString(B, radix) - if !ok { + numB, err = fpe.Num(B,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } + // Main Feistel Round, 10 times for i := 0; i < numRounds; i++ { // Calculate the dynamic parts of Q @@ -343,16 +353,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numB = numC } - A = numA.Text(radix) - B = numB.Text(radix) - - // Pad both A and B properly - A = strings.Repeat("0", int(u)-len(A)) + A - B = strings.Repeat("0", int(v)-len(B)) + B - - ret = A + B - - return ret, nil + return fpe.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // Decrypt decrypts the string X over the current FF1 parameters diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 90ed8bb..f1563f9 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -29,8 +29,13 @@ import ( // As Golang's sub-tests were introduced in Go 1.7, but this package will work with Go 1.6+, so I'm keeping sub-tests in a separate branch for now. +const ( + alpha_10 = "0123456789" + alpha_36 = "0123456789abcdefghijklmnopqrstuvwxyz" +) + type testVector struct { - radix int + alphabet string // Key and tweak are both hex-encoded strings key string @@ -43,21 +48,21 @@ type testVector struct { var testVectors = []testVector{ // AES-128 { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3C", "", "0123456789", "2433477484", }, { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3C", "39383736353433323130", "0123456789", "6124200773", }, { - 36, + alpha_36, "2B7E151628AED2A6ABF7158809CF4F3C", "3737373770717273373737", "0123456789abcdefghi", @@ -66,21 +71,21 @@ var testVectors = []testVector{ // AES-192 { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "", "0123456789", "2830668132", }, { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "39383736353433323130", "0123456789", "2496655549", }, { - 36, + alpha_36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "3737373770717273373737", "0123456789abcdefghi", @@ -89,21 +94,21 @@ var testVectors = []testVector{ // AES-256 { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "", "0123456789", "6657667009", }, { - 10, + alpha_10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "39383736353433323130", "0123456789", "1001623463", }, { - 36, + alpha_36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "3737373770717273373737", "0123456789abcdefghi", @@ -126,7 +131,7 @@ func TestEncrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -137,12 +142,13 @@ func TestEncrypt(t *testing.T) { } if ciphertext != testVector.ciphertext { - t.Fatalf("\nSample%d\nRadix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) + t.Fatalf("\nSample%d\nAlphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) } }) } } +/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -158,7 +164,7 @@ func TestDecrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -169,20 +175,20 @@ func TestDecrypt(t *testing.T) { } if plaintext != testVector.plaintext { - t.Fatalf("\nSample%d\nRadix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) + t.Fatalf("\nSample%d\nalphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) } }) } } -// These are for testing long inputs, which are not in the sandard test vectors +// These are for testing long inputs, which are not in the standard test vectors func TestLong(t *testing.T) { key, err := hex.DecodeString("2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94") tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(36, 16, key, tweak) + ff1, err := NewCipher(alpha_36, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -210,7 +216,7 @@ func TestIssue14(t *testing.T) { tweak, err := hex.DecodeString("D8E7920AFA330A73") - ff1, err := NewCipher(2, 8, key, tweak) + ff1, err := NewCipher("01", 8, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -247,7 +253,7 @@ func ExampleCipher_Encrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(10, 8, key, tweak) + FF1, err := NewCipher(alpha_10, 8, key, tweak) if err != nil { panic(err) } @@ -279,7 +285,7 @@ func ExampleCipher_Decrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(10, 8, key, tweak) + FF1, err := NewCipher(alpha_10, 8, key, tweak) if err != nil { panic(err) } @@ -313,7 +319,7 @@ func BenchmarkNewCipher(b *testing.B) { // 16 is an arbitrary number for maxTlen for n := 0; n < b.N; n++ { - NewCipher(testVector.radix, 16, key, tweak) + NewCipher(testVector.alphabet, 16, key, tweak) } }) } @@ -334,7 +340,7 @@ func BenchmarkEncrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -363,7 +369,7 @@ func BenchmarkDecrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -395,7 +401,7 @@ func BenchmarkE2ESample7(b *testing.B) { for n := 0; n < b.N; n++ { // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.radix, 16, key, tweak) + ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -421,7 +427,7 @@ func BenchmarkEncryptLong(b *testing.B) { tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(36, 16, key, tweak) + ff1, err := NewCipher(alpha_36, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -432,3 +438,5 @@ func BenchmarkEncryptLong(b *testing.B) { ff1.Encrypt("xs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwd") } } + +*/ diff --git a/ff1/numeral.go b/ff1/numeral.go deleted file mode 100644 index 2bef6ef..0000000 --- a/ff1/numeral.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "math/big" - "fmt" -) - -func num(s []uint16, radix uint64) (*big.Int, *big.Int, error) { - var big_radix, max, bv, x big.Int - if radix > 65536 { - return nil, nil, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) - } - - maxv := uint16(radix - 1) - big_radix.SetUint64(uint64(radix)) - max.SetInt64(1) - - for i, v := range s { - if v > maxv { - return nil, nil, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, v, maxv) - } - bv.SetUint64(uint64(v)) - x.Mul(&x, &big_radix) - x.Add(&x, &bv) - max.Mul(&max, &big_radix) - } - return &x, &max, nil -} - -func str(x *big.Int, m int, radix uint64) []uint16 { - r := make([]uint16, m) - - var big_radix, mod, v big.Int - v.Set(x) - big_radix.SetUint64(radix) - for i := range r { - v.DivMod(&v, &big_radix, &mod) - r[m-i-1] = uint16(mod.Uint64()) - } - return r -} - -func main() { - - s := []uint16{0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9} - n,max,err := num(s,10) - fmt.Println(n) - fmt.Println(max) - fmt.Println(err) - - v := str(n, len(s), 10) - fmt.Println(v) - fmt.Println(n) -} - diff --git a/numeral.go b/numeral.go new file mode 100644 index 0000000..3c4bb83 --- /dev/null +++ b/numeral.go @@ -0,0 +1,81 @@ +package fpe + +import ( + "fmt" + "math/big" +) + +// Num constructs a big.Int from an array of uint16, where each element represents +// one digit in the given radix. The array is arranged with the most significant digit in element 0, +// down to the least significant digit in element len-1. +func Num(s []uint16, radix uint64) (big.Int, error) { + var big_radix, bv, x big.Int + if radix > 65536 { + return x, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) + } + + maxv := uint16(radix - 1) + big_radix.SetUint64(uint64(radix)) + for i, v := range s { + if v > maxv { + return x, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, v, maxv) + } + bv.SetUint64(uint64(v)) + x.Mul(&x, &big_radix) + x.Add(&x, &bv) + } + return x, nil +} + +// Str populates an array of uint16 with digits representing big.Int x in the specified radix. +// The array is arranged with the most significant digint in element 0. +// The array is built from big.Int x from the least significant digit upwards. If the supplied +// array is too short, the most significant digits of x are quietly lost. +func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { + + var big_radix, mod, v big.Int + if radix > 65536 { + return r, fmt.Errorf("Radix (%d) too big: max supported radix os 65536", radix) + } + m := len(r) + v.Set(x) + big_radix.SetUint64(radix) + for i := range r { + v.DivMod(&v, &big_radix, &mod) + r[m-i-1] = uint16(mod.Uint64()) + } + if v.Sign() != 0 { + return r, fmt.Errorf("destination array too small: %s remains after conversion", &v) + } + return r, nil +} + +// EncodeNum constructs a big Int that represents the ordinal values of string s +// with respect to the alphabet built into the codec. +func EncodeNum(s string, c Codec) (*big.Int, error) { + numeral, err := c.Encode(s) + if err != nil { + return nil, err + } + v, err := Num(numeral,uint64(c.Radix())) + if err != nil { + return nil, err + } + return &v, nil +} + +// DecodeNum constructs a string from the ordinals encoded in two big Ints. +// len_a and len_b are the number of characters that should be built from the corresponding big Ints. +func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,error) { + ret := make([]uint16,len_a+len_b) + _, err := Str(a, ret[:len_a], uint64(c.Radix())) + if err != nil { + return "", err + } + _, err = Str(b, ret[len_a:], uint64(c.Radix())) + if err != nil { + return "", err + } + return c.Decode(ret) +} + diff --git a/numeral_test.go b/numeral_test.go new file mode 100644 index 0000000..3453d0d --- /dev/null +++ b/numeral_test.go @@ -0,0 +1,108 @@ +package fpe + +import ( + "fmt" + "math/big" + "reflect" + "testing" +) + +func TestEncode(t *testing.T) { + + testSpec := []struct { + radix uint64 + intv *big.Int + numeral []uint16 + }{ + { + 10, + big.NewInt(100), + []uint16{1, 0, 0}, + }, + { + 65536, + big.NewInt(0).Exp(big.NewInt(65536), big.NewInt(7), nil), + []uint16{1, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + for idx, spec := range testSpec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + v, err := Num(spec.numeral, spec.radix) + if err != nil { + t.Fatalf("error in Num: %s", err) + } + if v.Cmp(spec.intv) != 0 { + t.Fatalf("expected %v got %v", spec.intv, &v) + } + r := make([]uint16, len(spec.numeral)) + Str(&v, r, spec.radix) + if !reflect.DeepEqual(spec.numeral, r) { + t.Fatalf("Encode numeral incorrect: %v", r) + } + + }) + } +} + +func TestEncodeError(t *testing.T) { + + testSpec := []struct { + radix uint64 + intv *big.Int + numeral []uint16 + }{ + { + 10, + big.NewInt(100), + []uint16{10, 0, 0}, + }, + { + 65537, + big.NewInt(0).Exp(big.NewInt(65537), big.NewInt(7), nil), + []uint16{1, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + for idx, spec := range testSpec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + _, err := Num(spec.numeral, spec.radix) + if err == nil { + t.Fatalf("expected error in Num") + } + }) + } +} + +func TestDecodeError(t *testing.T) { + + testSpec := []struct { + radix uint64 + intv *big.Int + numeral []uint16 + }{ + { + 10, + big.NewInt(100), + []uint16{1, 0, 0}, + }, + { + 65537, + big.NewInt(0).Exp(big.NewInt(65537), big.NewInt(7), nil), + []uint16{1, 0, 0, 0, 0, 0, 0, 0}, + }, + } + + for idx, spec := range testSpec { + sampleNumber := idx + 1 + t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { + r := make([]uint16, 2) + _,err := Str(spec.intv, r, spec.radix) + if err == nil { + t.Fatalf("expected error in Str") + } + }) + } +} From 2ae6eda7dce0d9ed43f2b86295ac020034de2dc0 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 15:36:00 +0000 Subject: [PATCH 15/29] ff1 - decrypt working with numerals --- ff1/ff1.go | 48 +++++++++++++++++++----------------------------- ff1/ff1_test.go | 2 -- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/ff1/ff1.go b/ff1/ff1.go index 25aa823..811cfe7 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -28,7 +28,6 @@ import ( "errors" "math" "math/big" - "strings" "github.com/martin/fpe" "fmt" ) @@ -206,7 +205,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { binary.BigEndian.PutUint32(P[8:12], n) binary.BigEndian.PutUint32(P[12:lenP], uint32(t)) - // Determinte lengths of byte slices + // Determine lengths of byte slices // Q's length is known to always be t+b+1+numPad, to be multiple of 16 lenQ := t + b + 1 + numPad @@ -370,9 +369,16 @@ func (c Cipher) Decrypt(X string) (string, error) { func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { var ret string var err error - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) t := len(tweak) // Check if message length is within minLength and maxLength bounds @@ -385,22 +391,15 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix - - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } + radix := c.codec.Radix() // Calculate split point u := n / 2 v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] // Byte lengths b := int(math.Ceil(math.Ceil(float64(v)*math.Log2(float64(radix))) / 8)) @@ -432,7 +431,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { binary.BigEndian.PutUint32(P[8:12], n) binary.BigEndian.PutUint32(P[12:lenP], uint32(t)) - // Determinte lengths of byte slices + // Determine lengths of byte slices // Q's length is known to always be t+b+1+numPad, to be multiple of 16 lenQ := t + b + 1 + numPad @@ -497,13 +496,13 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - _, ok = numA.SetString(A, radix) - if !ok { + numA, err = fpe.Num(A,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } - _, ok = numB.SetString(B, radix) - if !ok { + numB, err = fpe.Num(B,uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -578,16 +577,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numA = numC } - A = numA.Text(radix) - B = numB.Text(radix) - - // Pad both A and B properly - A = strings.Repeat("0", int(u)-len(A)) + A - B = strings.Repeat("0", int(v)-len(B)) + B - - ret = A + B - - return ret, nil + return fpe.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // ciph defines how the main block cipher is called. diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index f1563f9..a3a6d17 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -148,7 +148,6 @@ func TestEncrypt(t *testing.T) { } } -/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -439,4 +438,3 @@ func BenchmarkEncryptLong(b *testing.B) { } } -*/ From 2a79be602423cf789759b461ff0cdeddc3110f13 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 15:49:48 +0000 Subject: [PATCH 16/29] copyright and pruning --- codec.go | 18 ++++++++++++++++++ codec_test.go | 19 +++++++++++++++++++ numeral.go | 38 +++++++++++++++++++++++--------------- numeral_test.go | 18 ++++++++++++++++++ 4 files changed, 78 insertions(+), 15 deletions(-) diff --git a/codec.go b/codec.go index b2e177d..da4755e 100644 --- a/codec.go +++ b/codec.go @@ -1,3 +1,21 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ package fpe import ( diff --git a/codec_test.go b/codec_test.go index acfde8f..2a8928d 100644 --- a/codec_test.go +++ b/codec_test.go @@ -1,3 +1,22 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ + package fpe import ( diff --git a/numeral.go b/numeral.go index 3c4bb83..b4a9507 100644 --- a/numeral.go +++ b/numeral.go @@ -1,3 +1,24 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ + +// Package fpe provides some encoding helpers for use +// in the FF1 and FF3 format-preserving encryption packages. package fpe import ( @@ -50,21 +71,8 @@ func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { return r, nil } -// EncodeNum constructs a big Int that represents the ordinal values of string s -// with respect to the alphabet built into the codec. -func EncodeNum(s string, c Codec) (*big.Int, error) { - numeral, err := c.Encode(s) - if err != nil { - return nil, err - } - v, err := Num(numeral,uint64(c.Radix())) - if err != nil { - return nil, err - } - return &v, nil -} - -// DecodeNum constructs a string from the ordinals encoded in two big Ints. +// DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices +// are encoded in the big Ints a and b. // len_a and len_b are the number of characters that should be built from the corresponding big Ints. func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,error) { ret := make([]uint16,len_a+len_b) diff --git a/numeral_test.go b/numeral_test.go index 3453d0d..4a27a27 100644 --- a/numeral_test.go +++ b/numeral_test.go @@ -1,3 +1,21 @@ +/* + +SPDX-Copyright: Copyright (c) Capital One Services, LLC +SPDX-License-Identifier: Apache-2.0 +Copyright 2017 Capital One Services, LLC + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and limitations under the License. + +*/ package fpe import ( From b2358683138e663619332241fc55e8ad05a92b2a Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 16:38:51 +0000 Subject: [PATCH 17/29] added test for unicode alphabet --- ff1/ff1.go | 37 +++++++++++++++--------- ff1/ff1_test.go | 76 +++++++++++++++++++++++++++++++++---------------- numeral.go | 15 +++++----- numeral_test.go | 4 +-- 4 files changed, 84 insertions(+), 48 deletions(-) diff --git a/ff1/ff1.go b/ff1/ff1.go index 811cfe7..bb2d0b9 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -26,10 +26,10 @@ import ( "crypto/cipher" "encoding/binary" "errors" + "fmt" + "github.com/capitalone/fpe" "math" "math/big" - "github.com/martin/fpe" - "fmt" ) // Note that this is strictly following the official NIST spec guidelines. In the linked PDF Appendix A (README.md), NIST recommends that radix^minLength >= 1,000,000. If you would like to follow that, change this parameter. @@ -72,9 +72,19 @@ type Cipher struct { cbcEncryptor cipher.BlockMode } -// NewCipher initializes a new FF1 Cipher for encryption or decryption use -// based on the radix, max tweak length, key and tweak parameters. -func NewCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { +const ( + // from func (*big.Int)SetString + legacy_alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" +) + +// NewCipher is provided for backwards compatibility for old client code. +func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) { + return NewAlphaCipher(legacy_alphabet[:radix], maxTLen, key, tweak) +} + +// NewAlphaCipher initializes a new FF1 Cipher for encryption or decryption use +// based on the alphabet, max tweak length, key and tweak parameters. +func NewAlphaCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) @@ -86,7 +96,7 @@ func NewCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, codec, err := fpe.NewCodec(alphabet) if err != nil { - return newCipher, fmt.Errorf("error making codec: %s", err ) + return newCipher, fmt.Errorf("error making codec: %s", err) } radix := codec.Radix() @@ -145,13 +155,13 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { var err error // String X contains a sequence of characters, where some characters - // might take up multiple bytes. Convert into an array of indices into + // might take up multiple bytes. Convert into an array of indices into // the alphabet embedded in the codec. Xn, err := c.codec.Encode(X) if err != nil { return ret, ErrStringNotInRadix } - + n := uint32(len(Xn)) t := len(tweak) @@ -270,17 +280,16 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpe.Num(A,uint64(radix)) + numA, err = fpe.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpe.Num(B,uint64(radix)) + numB, err = fpe.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - // Main Feistel Round, 10 times for i := 0; i < numRounds; i++ { // Calculate the dynamic parts of Q @@ -371,7 +380,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { var err error // String X contains a sequence of characters, where some characters - // might take up multiple bytes. Convert into an array of indices into + // might take up multiple bytes. Convert into an array of indices into // the alphabet embedded in the codec. Xn, err := c.codec.Encode(X) if err != nil { @@ -496,12 +505,12 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpe.Num(A,uint64(radix)) + numA, err = fpe.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpe.Num(B,uint64(radix)) + numB, err = fpe.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index a3a6d17..9e04cce 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -35,7 +35,7 @@ const ( ) type testVector struct { - alphabet string + radix int // Key and tweak are both hex-encoded strings key string @@ -48,21 +48,21 @@ type testVector struct { var testVectors = []testVector{ // AES-128 { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3C", "", "0123456789", "2433477484", }, { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3C", "39383736353433323130", "0123456789", "6124200773", }, { - alpha_36, + 36, "2B7E151628AED2A6ABF7158809CF4F3C", "3737373770717273373737", "0123456789abcdefghi", @@ -71,21 +71,21 @@ var testVectors = []testVector{ // AES-192 { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "", "0123456789", "2830668132", }, { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "39383736353433323130", "0123456789", "2496655549", }, { - alpha_36, + 36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F", "3737373770717273373737", "0123456789abcdefghi", @@ -94,21 +94,21 @@ var testVectors = []testVector{ // AES-256 { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "", "0123456789", "6657667009", }, { - alpha_10, + 10, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "39383736353433323130", "0123456789", "1001623463", }, { - alpha_36, + 36, "2B7E151628AED2A6ABF7158809CF4F3CEF4359D8D580AA4F7F036D6F04FC6A94", "3737373770717273373737", "0123456789abcdefghi", @@ -131,7 +131,7 @@ func TestEncrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -142,7 +142,7 @@ func TestEncrypt(t *testing.T) { } if ciphertext != testVector.ciphertext { - t.Fatalf("\nSample%d\nAlphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) + t.Fatalf("\nSample%d\nradix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nPlaintext:\t%s\nCiphertext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.plaintext, ciphertext, testVector.ciphertext) } }) } @@ -163,7 +163,7 @@ func TestDecrypt(t *testing.T) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -174,7 +174,7 @@ func TestDecrypt(t *testing.T) { } if plaintext != testVector.plaintext { - t.Fatalf("\nSample%d\nalphabet:\t\t%s\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.alphabet, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) + t.Fatalf("\nSample%d\nradix:\t\t%d\nKey:\t\t%s\nTweak:\t\t%s\nCiphertext:\t%s\nPlaintext:\t%s\nExpected:\t%s", sampleNumber, testVector.radix, testVector.key, testVector.tweak, testVector.ciphertext, plaintext, testVector.plaintext) } }) } @@ -187,7 +187,7 @@ func TestLong(t *testing.T) { tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(alpha_36, 16, key, tweak) + ff1, err := NewCipher(36, 16, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -215,7 +215,7 @@ func TestIssue14(t *testing.T) { tweak, err := hex.DecodeString("D8E7920AFA330A73") - ff1, err := NewCipher("01", 8, key, tweak) + ff1, err := NewCipher(2, 8, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -237,6 +237,35 @@ func TestIssue14(t *testing.T) { } } +// Alphabet can contain unicode characters +func TestUnicode(t *testing.T) { + key, err := hex.DecodeString("EF4359D8D580AA4F7F036D6F04FC6A94") + + tweak, err := hex.DecodeString("D8E7920AFA330A73") + + // 0-9 plus a 1-byte, 2-byte, 3-byte and 4-byte utf-8 chars + ff1, err := NewAlphaCipher("0123456789\u0024\u00A2\u0939\u10348", 8, key, tweak) + if err != nil { + t.Fatalf("Unable to create cipher: %v", err) + } + + plaintext := "0123456789\u0024\u00A2\u0939\u10348" + + ciphertext, err := ff1.Encrypt(plaintext) + if err != nil { + t.Fatalf("%v", err) + } + + decrypted, err := ff1.Decrypt(ciphertext) + if err != nil { + t.Fatalf("%v", err) + } + + if plaintext != decrypted { + t.Fatalf("Issue 14 Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + } +} + // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. @@ -252,7 +281,7 @@ func ExampleCipher_Encrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(alpha_10, 8, key, tweak) + FF1, err := NewCipher(10, 8, key, tweak) if err != nil { panic(err) } @@ -284,7 +313,7 @@ func ExampleCipher_Decrypt() { // Create a new FF1 cipher "object" // 10 is the radix/base, and 8 is the tweak length. - FF1, err := NewCipher(alpha_10, 8, key, tweak) + FF1, err := NewCipher(10, 8, key, tweak) if err != nil { panic(err) } @@ -318,7 +347,7 @@ func BenchmarkNewCipher(b *testing.B) { // 16 is an arbitrary number for maxTlen for n := 0; n < b.N; n++ { - NewCipher(testVector.alphabet, 16, key, tweak) + NewCipher(testVector.radix, 16, key, tweak) } }) } @@ -339,7 +368,7 @@ func BenchmarkEncrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -368,7 +397,7 @@ func BenchmarkDecrypt(b *testing.B) { } // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -400,7 +429,7 @@ func BenchmarkE2ESample7(b *testing.B) { for n := 0; n < b.N; n++ { // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(testVector.alphabet, 16, key, tweak) + ff1, err := NewCipher(testVector.radix, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -426,7 +455,7 @@ func BenchmarkEncryptLong(b *testing.B) { tweak, err := hex.DecodeString("") // 16 is an arbitrary number for maxTlen - ff1, err := NewCipher(alpha_36, 16, key, tweak) + ff1, err := NewCipher(36, 16, key, tweak) if err != nil { b.Fatalf("Unable to create cipher: %v", err) } @@ -437,4 +466,3 @@ func BenchmarkEncryptLong(b *testing.B) { ff1.Encrypt("xs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwdxs8a0azh2avyalyzuwd") } } - diff --git a/numeral.go b/numeral.go index b4a9507..75f9b7b 100644 --- a/numeral.go +++ b/numeral.go @@ -17,7 +17,7 @@ See the License for the specific language governing permissions and limitations */ -// Package fpe provides some encoding helpers for use +// Package fpe provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. package fpe @@ -52,7 +52,7 @@ func Num(s []uint16, radix uint64) (big.Int, error) { // The array is arranged with the most significant digint in element 0. // The array is built from big.Int x from the least significant digit upwards. If the supplied // array is too short, the most significant digits of x are quietly lost. -func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { +func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { var big_radix, mod, v big.Int if radix > 65536 { @@ -71,11 +71,11 @@ func Str(x *big.Int, r []uint16, radix uint64) ([]uint16,error) { return r, nil } -// DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices -// are encoded in the big Ints a and b. -// len_a and len_b are the number of characters that should be built from the corresponding big Ints. -func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,error) { - ret := make([]uint16,len_a+len_b) +// DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices +// are encoded in the big Ints a and b. +// len_a and len_b are the number of characters that should be built from the corresponding big Ints. +func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string, error) { + ret := make([]uint16, len_a+len_b) _, err := Str(a, ret[:len_a], uint64(c.Radix())) if err != nil { return "", err @@ -86,4 +86,3 @@ func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string,er } return c.Decode(ret) } - diff --git a/numeral_test.go b/numeral_test.go index 4a27a27..12510de 100644 --- a/numeral_test.go +++ b/numeral_test.go @@ -116,8 +116,8 @@ func TestDecodeError(t *testing.T) { for idx, spec := range testSpec { sampleNumber := idx + 1 t.Run(fmt.Sprintf("Sample%d", sampleNumber), func(t *testing.T) { - r := make([]uint16, 2) - _,err := Str(spec.intv, r, spec.radix) + r := make([]uint16, 2) + _, err := Str(spec.intv, r, spec.radix) if err == nil { t.Fatalf("expected error in Str") } From a87299c0c48c35b5bd2883672422ca3619f1d289 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 17:17:52 +0000 Subject: [PATCH 18/29] updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 08421c4..a5d9014 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ func main() { // Create a new FF1 cipher "object" // Alphabet defines the supported character set, and 8 is the tweak length. - FF1, err := ff1.NewCipher(alphabet, 8, key, tweak) + FF1, err := ff1.NewAlphaCipher(alphabet, 8, key, tweak) if err != nil { panic(err) } From ed191c69eea2c51abb1d41783724ee522da5a3a7 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Fri, 21 Dec 2018 17:22:48 +0000 Subject: [PATCH 19/29] test tidy --- ff1/ff1_test.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 9e04cce..9c03ba4 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -29,11 +29,6 @@ import ( // As Golang's sub-tests were introduced in Go 1.7, but this package will work with Go 1.6+, so I'm keeping sub-tests in a separate branch for now. -const ( - alpha_10 = "0123456789" - alpha_36 = "0123456789abcdefghijklmnopqrstuvwxyz" -) - type testVector struct { radix int @@ -262,7 +257,7 @@ func TestUnicode(t *testing.T) { } if plaintext != decrypted { - t.Fatalf("Issue 14 Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + t.Fatalf("TestUnicode Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) } } From 8ee45f807cf550f2da2d17a827ed4ec05e0ab277 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Sat, 22 Dec 2018 10:40:51 +0000 Subject: [PATCH 20/29] FF3 - encrypt --- codec.go | 8 +++- ff3/ff3.go | 99 ++++++++++++++++++++++++++++++++----------------- ff3/ff3_test.go | 2 + numeral.go | 46 ++++++++++++++++++++++- 4 files changed, 118 insertions(+), 37 deletions(-) diff --git a/codec.go b/codec.go index da4755e..1fcb2de 100644 --- a/codec.go +++ b/codec.go @@ -67,7 +67,13 @@ func (a *Codec) Radix() int { // It is an error for the supplied string to contain characters than are not // in the alphabet. func (a *Codec) Encode(s string) ([]uint16, error) { - ret := make([]uint16, utf8.RuneCountInString(s)) + n := utf8.RuneCountInString(s) + c := n + if n%2 == 1 { + // ensure the numeral array has even-sized capacity for FF3 + c++ + } + ret := make([]uint16, n, c) var ok bool i := 0 diff --git a/ff3/ff3.go b/ff3/ff3.go index ad5cbdd..7dd9a1b 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -25,6 +25,8 @@ import ( "crypto/aes" "crypto/cipher" "errors" + "fmt" + "github.com/capitalone/fpe" "math" "math/big" ) @@ -51,7 +53,7 @@ var ( // using a particular key, radix, and tweak type Cipher struct { tweak []byte - radix int + codec fpe.Codec minLen uint32 maxLen uint32 @@ -59,9 +61,19 @@ type Cipher struct { aesBlock cipher.Block } -// NewCipher initializes a new FF3 Cipher for encryption or decryption use -// based on the radix, key and tweak parameters. +const ( + // from func (*big.Int)SetString + legacy_alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" +) + +// NewCipher is provided for backwards compatibility for old client code. func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { + return NewAlphaCipher(legacy_alphabet[:radix], key, tweak) +} + +// NewAlphaCipher initializes a new FF3 Cipher for encryption or decryption use +// based on the alphabet, max tweak length, key and tweak parameters. +func NewAlphaCipher(alphabet string, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) @@ -71,9 +83,16 @@ func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - // While FF3 allows radices in [2, 2^16], there is a practical limit to 36 (alphanumeric) because the Go math/big library only supports up to base 36. - if (radix < 2) || (radix > big.MaxBase) { - return newCipher, errors.New("radix must be between 2 and 36, inclusive") + codec, err := fpe.NewCodec(alphabet) + if err != nil { + return newCipher, fmt.Errorf("error making codec: %s", err) + } + + radix := codec.Radix() + + // FF3 allows radices in [2, 2^16] + if (radix < 2) || (radix > 65536) { + return newCipher, errors.New("radix must be between 2 and 65536, inclusive") } // Make sure the given the length of tweak in bits is 64 @@ -99,7 +118,7 @@ func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { } newCipher.tweak = tweak - newCipher.radix = radix + newCipher.codec = codec newCipher.minLen = minLen newCipher.maxLen = maxLen newCipher.aesBlock = aesBlock @@ -120,9 +139,16 @@ func (c Cipher) Encrypt(X string) (string, error) { // use-case of FPE for things like credit card numbers. func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { var ret string - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) // Check if message length is within minLength and maxLength bounds // TODO BUG: when n==c.maxLen, it breaks. For now, I'm changing @@ -136,22 +162,18 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix - - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } + radix := c.codec.Radix() // Calculate split point u := uint32(math.Ceil(float64(n) / 2)) v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] + + // C must be large enough to hold either A or B + C := make([]uint16, u) // Split the tweak Tl := tweak[:halfTweakLen] @@ -200,8 +222,8 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(B) with padding - _, ok = numB.SetString(rev(B), radix) - if !ok { + numB, err = fpe.NumRev(B, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -225,8 +247,8 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numY.SetBytes(S[:]) // Calculate c - _, ok = numC.SetString(rev(A), radix) - if !ok { + numC, err = fpe.NumRev(A, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -238,22 +260,29 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numC.Mod(&numC, &numModV) } - C := numC.Text(c.radix) - - // Need to pad the text with leading 0s first to make sure it's the correct length - for len(C) < int(m) { - C = "0" + C + C = C[:m] + _, err := fpe.StrRev(&numC, C, uint64(c.codec.Radix())) + if err != nil { + return "", err } - C = rev(C) // Final steps - A = B - B = C + A, B, C = B, C, A + } + + // convert the numeral arrays back to strings + strA, err := c.codec.Decode(A) + if err != nil { + return "", err + } + + strB, err := c.codec.Decode(B) + if err != nil { + return "", err } - ret = A + B + return strA + strB, nil - return ret, nil } // Decrypt decrypts the string X over the current FF3 parameters @@ -285,7 +314,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { return ret, ErrTweakLengthInvalid } - radix := c.radix + radix := c.codec.Radix() // Check if the message is in the current radix var numX big.Int @@ -390,7 +419,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numC.Mod(&numC, &numModV) } - C := numC.Text(c.radix) + C := numC.Text(c.codec.Radix()) // Need to pad the text with leading 0s first to make sure it's the correct length for len(C) < int(m) { diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index ea32b33..0f687b3 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -173,6 +173,7 @@ func TestEncrypt(t *testing.T) { } } +/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -322,3 +323,4 @@ func BenchmarkDecrypt(b *testing.B) { }) } } +*/ diff --git a/numeral.go b/numeral.go index 75f9b7b..2da9f65 100644 --- a/numeral.go +++ b/numeral.go @@ -48,8 +48,30 @@ func Num(s []uint16, radix uint64) (big.Int, error) { return x, nil } +// NumRev constructs a big.Int from an array of uint16, where each element represents +// one digit in the given radix. The array is arranged with the least significant digit in element 0, +// down to the most significant digit in element len-1. +func NumRev(s []uint16, radix uint64) (big.Int, error) { + var big_radix, bv, x big.Int + if radix > 65536 { + return x, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) + } + + maxv := uint16(radix - 1) + big_radix.SetUint64(uint64(radix)) + for i := len(s) - 1; i >= 0; i-- { + if s[i] > maxv { + return x, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, s[i], maxv) + } + bv.SetUint64(uint64(s[i])) + x.Mul(&x, &big_radix) + x.Add(&x, &bv) + } + return x, nil +} + // Str populates an array of uint16 with digits representing big.Int x in the specified radix. -// The array is arranged with the most significant digint in element 0. +// The array is arranged with the most significant digit in element 0. // The array is built from big.Int x from the least significant digit upwards. If the supplied // array is too short, the most significant digits of x are quietly lost. func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { @@ -71,6 +93,28 @@ func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { return r, nil } +// StrRev populates an array of uint16 with digits representing big.Int x in the specified radix. +// The array is arranged with the least significant digit in element 0. +// The array is built from big.Int x from the least significant digit upwards. If the supplied +// array is too short, the most significant digits of x are quietly lost. +func StrRev(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { + + var big_radix, mod, v big.Int + if radix > 65536 { + return r, fmt.Errorf("Radix (%d) too big: max supported radix os 65536", radix) + } + v.Set(x) + big_radix.SetUint64(radix) + for i := range r { + v.DivMod(&v, &big_radix, &mod) + r[i] = uint16(mod.Uint64()) + } + if v.Sign() != 0 { + return r, fmt.Errorf("destination array too small: %s remains after conversion", &v) + } + return r, nil +} + // DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices // are encoded in the big Ints a and b. // len_a and len_b are the number of characters that should be built from the corresponding big Ints. From de47b46c503be968bc30a200bfa53da7a202d5e5 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Sat, 22 Dec 2018 10:53:57 +0000 Subject: [PATCH 21/29] FF3 - decrypt --- ff3/ff3.go | 62 ++++++++++++++++++++++++++++--------------------- ff3/ff3_test.go | 2 +- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/ff3/ff3.go b/ff3/ff3.go index 7dd9a1b..57a1287 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -298,9 +298,16 @@ func (c Cipher) Decrypt(X string) (string, error) { // use-case of FPE for things like credit card numbers. func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { var ret string - var ok bool - n := uint32(len(X)) + // String X contains a sequence of characters, where some characters + // might take up multiple bytes. Convert into an array of indices into + // the alphabet embedded in the codec. + Xn, err := c.codec.Encode(X) + if err != nil { + return ret, ErrStringNotInRadix + } + + n := uint32(len(Xn)) // Check if message length is within minLength and maxLength bounds // TODO BUG: when n==c.maxLen, it breaks. For now, I'm changing @@ -316,20 +323,16 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { radix := c.codec.Radix() - // Check if the message is in the current radix - var numX big.Int - _, ok = numX.SetString(X, radix) - if !ok { - return ret, ErrStringNotInRadix - } - // Calculate split point u := uint32(math.Ceil(float64(n) / 2)) v := n - u // Split the message - A := X[:u] - B := X[u:] + A := Xn[:u] + B := Xn[u:] + + // C must be large enough to hold either A or B + C := make([]uint16, u) // Split the tweak Tl := tweak[:halfTweakLen] @@ -378,8 +381,8 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(A) with padding - _, ok = numA.SetString(rev(A), radix) - if !ok { + numA, err = fpe.NumRev(A, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -402,12 +405,9 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { // Calculate numY numY.SetBytes(S[:]) - // Calculate numY - numY.SetBytes(S[:]) - // Calculate c - _, ok = numC.SetString(rev(B), radix) - if !ok { + numC, err = fpe.NumRev(B, uint64(radix)) + if err != nil { return ret, ErrStringNotInRadix } @@ -419,20 +419,28 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numC.Mod(&numC, &numModV) } - C := numC.Text(c.codec.Radix()) - - // Need to pad the text with leading 0s first to make sure it's the correct length - for len(C) < int(m) { - C = "0" + C + C = C[:m] + _, err := fpe.StrRev(&numC, C, uint64(c.codec.Radix())) + if err != nil { + return "", err } - C = rev(C) // Final steps - B = A - A = C + B, A, C = A, C, B } - return A + B, nil + // convert the numeral arrays back to strings + strA, err := c.codec.Decode(A) + if err != nil { + return "", err + } + + strB, err := c.codec.Decode(B) + if err != nil { + return "", err + } + + return strA + strB, nil } // rev reverses a string diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index 0f687b3..a54f464 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -173,7 +173,6 @@ func TestEncrypt(t *testing.T) { } } -/* func TestDecrypt(t *testing.T) { for idx, testVector := range testVectors { sampleNumber := idx + 1 @@ -205,6 +204,7 @@ func TestDecrypt(t *testing.T) { } } +/* // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. From 7919d1b52e0c15a06ad31482323efecea02faaf7 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Sat, 22 Dec 2018 10:55:04 +0000 Subject: [PATCH 22/29] FF3 -reinstate tests --- ff3/ff3_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index a54f464..ea32b33 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -204,7 +204,6 @@ func TestDecrypt(t *testing.T) { } } -/* // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. @@ -323,4 +322,3 @@ func BenchmarkDecrypt(b *testing.B) { }) } } -*/ From 1118207906f2fa090ce4811756553732ca897218 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 24 Dec 2018 08:32:20 +0000 Subject: [PATCH 23/29] test numeral bit lengths --- codec.go | 8 +++--- codec_test.go | 38 ++++++++++++++++++++++++++++- ff1/ff1.go | 4 +-- ff1/ff1_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 7 deletions(-) diff --git a/codec.go b/codec.go index 1fcb2de..ed83e12 100644 --- a/codec.go +++ b/codec.go @@ -40,14 +40,14 @@ func NewCodec(s string) (Codec, error) { ret.rtu = make(map[rune]uint16) ret.utr = make([]rune, utf8.RuneCountInString(s)) - var i uint16 + var i uint32 for _, rv := range s { // duplicates are tolerated, but ignored. if _, ok := ret.rtu[rv]; !ok { ret.utr[i] = rv - ret.rtu[rv] = i - if i == 65535 { - return ret, fmt.Errorf("alphabet must contain fewer than 65536 characters") + ret.rtu[rv] = uint16(i) + if i == 65536 { + return ret, fmt.Errorf("alphabet must contain no more than 65536 characters") } i++ } diff --git a/codec_test.go b/codec_test.go index 2a8928d..a670660 100644 --- a/codec_test.go +++ b/codec_test.go @@ -20,9 +20,11 @@ See the License for the specific language governing permissions and limitations package fpe import ( + "bytes" "fmt" "reflect" "testing" + "unicode/utf8" ) var testCodec = []struct { @@ -118,8 +120,42 @@ func TestEncoder(t *testing.T) { _, err = al.Encode(spec.input) if err == nil { - t.Fatalf("Encode unexpectly succeeded: input '%s', alphabet '%s'", spec.input, spec.alphabet) + t.Fatalf("Encode unexpectedly succeeded: input '%s', alphabet '%s'", spec.input, spec.alphabet) } }) } } + +func TestLargeAlphabet(t *testing.T) { + var alphabet bytes.Buffer + + nr := 0 + for i := 0; i < 100000; i++ { + if utf8.ValidRune(rune(i)) { + s := string(rune(i)) + nr++ + alphabet.WriteString(s) + if nr == 65536 { + break + } + } + } + + al, err := NewCodec(alphabet.String()) + if err != nil { + t.Fatalf("Error making codec: %s", err) + } + if al.Radix() != 65536 { + t.Fatalf("Incorrect radix %d ", al.Radix()) + } + + nml, err := al.Encode("hello world") + if err != nil { + t.Fatalf("Unable to encode: %s", err) + } + + _, err = al.Decode(nml) + if err != nil { + t.Fatalf("Unable to decode: %s", err) + } +} diff --git a/ff1/ff1.go b/ff1/ff1.go index bb2d0b9..4515bf4 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -116,8 +116,8 @@ func NewAlphaCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cip var maxLen uint32 = math.MaxUint32 - // Make sure 2 <= minLength <= maxLength < 2^32 is satisfied - if (minLen < 2) || (maxLen < minLen) || (maxLen > math.MaxUint32) { + // Make sure minLength <= maxLength < 2^32 is satisfied + if (maxLen < minLen) || (maxLen > math.MaxUint32) { return newCipher, errors.New("minLen invalid, adjust your radix") } diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 9c03ba4..7206a36 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -20,9 +20,12 @@ See the License for the specific language governing permissions and limitations package ff1 import ( + "bytes" "encoding/hex" "fmt" "testing" + "unicode/utf8" + "strings" ) // Test vectors taken from here: http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/FF1samples.pdf @@ -261,6 +264,68 @@ func TestUnicode(t *testing.T) { } } +func TestAlphabetSizes(t *testing.T) { + // encryption deals with numeral values encoded in ceil(log(radix))-sized + // bit strings, up to 16 bits in length - the number of bits in a uint16. + // This test exercises behaviour for all bitstring lengths from 1 to 16. + + key, _ := hex.DecodeString("EF4359D8D580AA4F7F036D6F04FC6A94") + + tweak, _ := hex.DecodeString("D8E7920AFA330A73") + + for s := uint(1); s < 17; s++ { + a, err := buildAlphabet(1 << s) + if err != nil { + t.Fatalf("TestAlphabetSizes: %s", err) + } + + ff1, err := NewAlphaCipher(a, 8, key, tweak) + if err != nil { + t.Fatalf("Unable to create cipher: %v", err) + } + + plaintext := strings.Repeat(string(rune(0)),10) + ciphertext, err := ff1.Encrypt(plaintext) + if err != nil { + t.Fatalf("%v", err) + } + + decrypted, err := ff1.Decrypt(ciphertext) + if err != nil { + t.Fatalf("%v", err) + } + + if plaintext != decrypted { + t.Fatalf("TestUnicode Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + } + + } + +} + +func buildAlphabet(n int) (string, error) { + // Not every code-point can be encoded as utf-8 string. + // For example u+DC00 - u+DFFF contains "isolated surrogate code points" + // that have no string interpretation. + // (https://www.unicode.org/charts/PDF/UDC00.pdf) + // + // Loop through a large number of code points and collect + // up to n code points with valid interpretations. + var alphabet bytes.Buffer + nr := 0 + for i := 0; i < 100000; i++ { + if utf8.ValidRune(rune(i)) { + s := string(rune(i)) + nr++ + alphabet.WriteString(s) + if nr == n { + return alphabet.String(), nil + } + } + } + return alphabet.String(), fmt.Errorf("Failed to collect %d validrunes: only %d collected", n, nr) +} + // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. From 2249876c637631973ebb9ec5efd163a97f64556a Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 24 Dec 2018 08:39:56 +0000 Subject: [PATCH 24/29] ff3 - test numeral bit lengths --- ff1/ff1_test.go | 4 +-- ff3/ff3.go | 4 +-- ff3/ff3_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 7206a36..05b2601 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -23,9 +23,9 @@ import ( "bytes" "encoding/hex" "fmt" + "strings" "testing" "unicode/utf8" - "strings" ) // Test vectors taken from here: http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/FF1samples.pdf @@ -284,7 +284,7 @@ func TestAlphabetSizes(t *testing.T) { t.Fatalf("Unable to create cipher: %v", err) } - plaintext := strings.Repeat(string(rune(0)),10) + plaintext := strings.Repeat(string(rune(0)), 10) ciphertext, err := ff1.Encrypt(plaintext) if err != nil { t.Fatalf("%v", err) diff --git a/ff3/ff3.go b/ff3/ff3.go index 57a1287..4320ab2 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -105,8 +105,8 @@ func NewAlphaCipher(alphabet string, key []byte, tweak []byte) (Cipher, error) { maxLen := uint32(math.Floor((192 / math.Log2(float64(radix))))) - // Make sure 2 <= minLength <= maxLength < 2*floor(log base radix of 2^96) is satisfied - if (minLen < 2) || (maxLen < minLen) || (float64(maxLen) > (192 / math.Log2(float64(radix)))) { + // Make sure minLength <= maxLength < 2*floor(log base radix of 2^96) is satisfied + if (maxLen < minLen) || (float64(maxLen) > (192 / math.Log2(float64(radix)))) { return newCipher, errors.New("minLen or maxLen invalid, adjust your radix") } diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index ea32b33..1de2d33 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -20,9 +20,12 @@ See the License for the specific language governing permissions and limitations package ff3 import ( + "bytes" "encoding/hex" "fmt" + "strings" "testing" + "unicode/utf8" ) // Test vectors taken from here: http://csrc.nist.gov/groups/ST/toolkit/documents/Examples/FF3samples.pdf @@ -204,6 +207,68 @@ func TestDecrypt(t *testing.T) { } } +func TestAlphabetSizes(t *testing.T) { + // encryption deals with numeral values encoded in ceil(log(radix))-sized + // bit strings, up to 16 bits in length - the number of bits in a uint16. + // This test exercises behaviour for all bitstring lengths from 1 to 16. + + key, _ := hex.DecodeString("EF4359D8D580AA4F7F036D6F04FC6A94") + + tweak, _ := hex.DecodeString("D8E7920AFA330A73") + + for s := uint(1); s < 17; s++ { + a, err := buildAlphabet(1 << s) + if err != nil { + t.Fatalf("TestAlphabetSizes: %s", err) + } + + ff3, err := NewAlphaCipher(a, key, tweak) + if err != nil { + t.Fatalf("Unable to create cipher: %v", err) + } + + plaintext := strings.Repeat(string(rune(0)), 10) + ciphertext, err := ff3.Encrypt(plaintext) + if err != nil { + t.Fatalf("%v", err) + } + + decrypted, err := ff3.Decrypt(ciphertext) + if err != nil { + t.Fatalf("%v", err) + } + + if plaintext != decrypted { + t.Fatalf("TestUnicode Decrypt Failed. \n Expected: %v \n Got: %v \n", plaintext, decrypted) + } + + } + +} + +func buildAlphabet(n int) (string, error) { + // Not every code-point can be encoded as utf-8 string. + // For example u+DC00 - u+DFFF contains "isolated surrogate code points" + // that have no string interpretation. + // (https://www.unicode.org/charts/PDF/UDC00.pdf) + // + // Loop through a large number of code points and collect + // up to n code points with valid interpretations. + var alphabet bytes.Buffer + nr := 0 + for i := 0; i < 100000; i++ { + if utf8.ValidRune(rune(i)) { + s := string(rune(i)) + nr++ + alphabet.WriteString(s) + if nr == n { + return alphabet.String(), nil + } + } + } + return alphabet.String(), fmt.Errorf("Failed to collect %d validrunes: only %d collected", n, nr) +} + // Note: panic(err) is just used for example purposes. func ExampleCipher_Encrypt() { // Key and tweak should be byte arrays. Put your key and tweak here. From fa7c425d7d7836909fa2df51e2db16b8cba76785 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 31 Dec 2018 18:33:34 +0000 Subject: [PATCH 25/29] lint fixes --- codec.go | 3 +++ ff1/ff1.go | 4 ++-- ff3/ff3.go | 4 ++-- numeral.go | 34 +++++++++++++++++----------------- 4 files changed, 24 insertions(+), 21 deletions(-) diff --git a/codec.go b/codec.go index ed83e12..1f89cd7 100644 --- a/codec.go +++ b/codec.go @@ -16,6 +16,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ + +// Package fpe provides some encoding helpers for use +// in the FF1 and FF3 format-preserving encryption packages. package fpe import ( diff --git a/ff1/ff1.go b/ff1/ff1.go index 4515bf4..09853e6 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -74,12 +74,12 @@ type Cipher struct { const ( // from func (*big.Int)SetString - legacy_alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" + legacyAlphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" ) // NewCipher is provided for backwards compatibility for old client code. func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) { - return NewAlphaCipher(legacy_alphabet[:radix], maxTLen, key, tweak) + return NewAlphaCipher(legacyAlphabet[:radix], maxTLen, key, tweak) } // NewAlphaCipher initializes a new FF1 Cipher for encryption or decryption use diff --git a/ff3/ff3.go b/ff3/ff3.go index 4320ab2..a72da65 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -63,12 +63,12 @@ type Cipher struct { const ( // from func (*big.Int)SetString - legacy_alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" + legacyAlphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRTSUVWXYZ" ) // NewCipher is provided for backwards compatibility for old client code. func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { - return NewAlphaCipher(legacy_alphabet[:radix], key, tweak) + return NewAlphaCipher(legacyAlphabet[:radix], key, tweak) } // NewAlphaCipher initializes a new FF3 Cipher for encryption or decryption use diff --git a/numeral.go b/numeral.go index 2da9f65..f02ce71 100644 --- a/numeral.go +++ b/numeral.go @@ -30,19 +30,19 @@ import ( // one digit in the given radix. The array is arranged with the most significant digit in element 0, // down to the least significant digit in element len-1. func Num(s []uint16, radix uint64) (big.Int, error) { - var big_radix, bv, x big.Int + var bigRadix, bv, x big.Int if radix > 65536 { return x, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) } maxv := uint16(radix - 1) - big_radix.SetUint64(uint64(radix)) + bigRadix.SetUint64(uint64(radix)) for i, v := range s { if v > maxv { return x, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, v, maxv) } bv.SetUint64(uint64(v)) - x.Mul(&x, &big_radix) + x.Mul(&x, &bigRadix) x.Add(&x, &bv) } return x, nil @@ -52,19 +52,19 @@ func Num(s []uint16, radix uint64) (big.Int, error) { // one digit in the given radix. The array is arranged with the least significant digit in element 0, // down to the most significant digit in element len-1. func NumRev(s []uint16, radix uint64) (big.Int, error) { - var big_radix, bv, x big.Int + var bigRadix, bv, x big.Int if radix > 65536 { return x, fmt.Errorf("Radix (%d) too big: max supported radix is 65536", radix) } maxv := uint16(radix - 1) - big_radix.SetUint64(uint64(radix)) + bigRadix.SetUint64(uint64(radix)) for i := len(s) - 1; i >= 0; i-- { if s[i] > maxv { return x, fmt.Errorf("Value at %d out of range: got %d - expected 0..%d", i, s[i], maxv) } bv.SetUint64(uint64(s[i])) - x.Mul(&x, &big_radix) + x.Mul(&x, &bigRadix) x.Add(&x, &bv) } return x, nil @@ -76,15 +76,15 @@ func NumRev(s []uint16, radix uint64) (big.Int, error) { // array is too short, the most significant digits of x are quietly lost. func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { - var big_radix, mod, v big.Int + var bigRadix, mod, v big.Int if radix > 65536 { return r, fmt.Errorf("Radix (%d) too big: max supported radix os 65536", radix) } m := len(r) v.Set(x) - big_radix.SetUint64(radix) + bigRadix.SetUint64(radix) for i := range r { - v.DivMod(&v, &big_radix, &mod) + v.DivMod(&v, &bigRadix, &mod) r[m-i-1] = uint16(mod.Uint64()) } if v.Sign() != 0 { @@ -99,14 +99,14 @@ func Str(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { // array is too short, the most significant digits of x are quietly lost. func StrRev(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { - var big_radix, mod, v big.Int + var bigRadix, mod, v big.Int if radix > 65536 { return r, fmt.Errorf("Radix (%d) too big: max supported radix os 65536", radix) } v.Set(x) - big_radix.SetUint64(radix) + bigRadix.SetUint64(radix) for i := range r { - v.DivMod(&v, &big_radix, &mod) + v.DivMod(&v, &bigRadix, &mod) r[i] = uint16(mod.Uint64()) } if v.Sign() != 0 { @@ -117,14 +117,14 @@ func StrRev(x *big.Int, r []uint16, radix uint64) ([]uint16, error) { // DecodeNum constructs a string from indices into the alphabet embedded in the Codec. The indices // are encoded in the big Ints a and b. -// len_a and len_b are the number of characters that should be built from the corresponding big Ints. -func DecodeNum(a *big.Int, len_a int, b *big.Int, len_b int, c Codec) (string, error) { - ret := make([]uint16, len_a+len_b) - _, err := Str(a, ret[:len_a], uint64(c.Radix())) +// lenA and lenB are the number of characters that should be built from the corresponding big Ints. +func DecodeNum(a *big.Int, lenA int, b *big.Int, lenB int, c Codec) (string, error) { + ret := make([]uint16, lenA+lenB) + _, err := Str(a, ret[:lenA], uint64(c.Radix())) if err != nil { return "", err } - _, err = Str(b, ret[len_a:], uint64(c.Radix())) + _, err = Str(b, ret[lenA:], uint64(c.Radix())) if err != nil { return "", err } From 0c9ddfed7ecefb47964bbbb0b0c60e5f76c75b58 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 31 Dec 2018 18:53:18 +0000 Subject: [PATCH 26/29] separate fpeutils --- ff1/ff1.go | 18 +++++++++--------- ff3/ff3.go | 18 +++++++++--------- codec.go => fpeutils/codec.go | 4 ++-- codec_test.go => fpeutils/codec_test.go | 4 +++- numeral.go => fpeutils/numeral.go | 4 ++-- numeral_test.go => fpeutils/numeral_test.go | 5 ++++- 6 files changed, 29 insertions(+), 24 deletions(-) rename codec.go => fpeutils/codec.go (97%) rename codec_test.go => fpeutils/codec_test.go (96%) rename numeral.go => fpeutils/numeral.go (98%) rename numeral_test.go => fpeutils/numeral_test.go (95%) diff --git a/ff1/ff1.go b/ff1/ff1.go index 09853e6..19168d5 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -27,7 +27,7 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/capitalone/fpe" + "github.com/capitalone/fpe/fpeutils" "math" "math/big" ) @@ -62,7 +62,7 @@ type cbcMode interface { // using a particular key, radix, and tweak type Cipher struct { tweak []byte - codec fpe.Codec + codec fpeutils.Codec radix int minLen uint32 maxLen uint32 @@ -94,7 +94,7 @@ func NewAlphaCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cip return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - codec, err := fpe.NewCodec(alphabet) + codec, err := fpeutils.NewCodec(alphabet) if err != nil { return newCipher, fmt.Errorf("error making codec: %s", err) } @@ -280,12 +280,12 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpe.Num(A, uint64(radix)) + numA, err = fpeutils.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpe.Num(B, uint64(radix)) + numB, err = fpeutils.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -361,7 +361,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numB = numC } - return fpe.DecodeNum(&numA, len(A), &numB, len(B), c.codec) + return fpeutils.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // Decrypt decrypts the string X over the current FF1 parameters @@ -505,12 +505,12 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpe.Num(A, uint64(radix)) + numA, err = fpeutils.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpe.Num(B, uint64(radix)) + numB, err = fpeutils.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -586,7 +586,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numA = numC } - return fpe.DecodeNum(&numA, len(A), &numB, len(B), c.codec) + return fpeutils.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // ciph defines how the main block cipher is called. diff --git a/ff3/ff3.go b/ff3/ff3.go index a72da65..eb09255 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -26,7 +26,7 @@ import ( "crypto/cipher" "errors" "fmt" - "github.com/capitalone/fpe" + "github.com/capitalone/fpe/fpeutils" "math" "math/big" ) @@ -53,7 +53,7 @@ var ( // using a particular key, radix, and tweak type Cipher struct { tweak []byte - codec fpe.Codec + codec fpeutils.Codec minLen uint32 maxLen uint32 @@ -83,7 +83,7 @@ func NewAlphaCipher(alphabet string, key []byte, tweak []byte) (Cipher, error) { return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - codec, err := fpe.NewCodec(alphabet) + codec, err := fpeutils.NewCodec(alphabet) if err != nil { return newCipher, fmt.Errorf("error making codec: %s", err) } @@ -222,7 +222,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(B) with padding - numB, err = fpe.NumRev(B, uint64(radix)) + numB, err = fpeutils.NumRev(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -247,7 +247,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numY.SetBytes(S[:]) // Calculate c - numC, err = fpe.NumRev(A, uint64(radix)) + numC, err = fpeutils.NumRev(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -261,7 +261,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { } C = C[:m] - _, err := fpe.StrRev(&numC, C, uint64(c.codec.Radix())) + _, err := fpeutils.StrRev(&numC, C, uint64(c.codec.Radix())) if err != nil { return "", err } @@ -381,7 +381,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(A) with padding - numA, err = fpe.NumRev(A, uint64(radix)) + numA, err = fpeutils.NumRev(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -406,7 +406,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numY.SetBytes(S[:]) // Calculate c - numC, err = fpe.NumRev(B, uint64(radix)) + numC, err = fpeutils.NumRev(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -420,7 +420,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { } C = C[:m] - _, err := fpe.StrRev(&numC, C, uint64(c.codec.Radix())) + _, err := fpeutils.StrRev(&numC, C, uint64(c.codec.Radix())) if err != nil { return "", err } diff --git a/codec.go b/fpeutils/codec.go similarity index 97% rename from codec.go rename to fpeutils/codec.go index 1f89cd7..1948556 100644 --- a/codec.go +++ b/fpeutils/codec.go @@ -17,9 +17,9 @@ See the License for the specific language governing permissions and limitations */ -// Package fpe provides some encoding helpers for use +// Package fpeutils provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. -package fpe +package fpeutils import ( "fmt" diff --git a/codec_test.go b/fpeutils/codec_test.go similarity index 96% rename from codec_test.go rename to fpeutils/codec_test.go index a670660..34d2eb0 100644 --- a/codec_test.go +++ b/fpeutils/codec_test.go @@ -17,7 +17,9 @@ See the License for the specific language governing permissions and limitations */ -package fpe +// Package fpeutils provides some encoding helpers for use +// in the FF1 and FF3 format-preserving encryption packages. +package fpeutils import ( "bytes" diff --git a/numeral.go b/fpeutils/numeral.go similarity index 98% rename from numeral.go rename to fpeutils/numeral.go index f02ce71..7c30f64 100644 --- a/numeral.go +++ b/fpeutils/numeral.go @@ -17,9 +17,9 @@ See the License for the specific language governing permissions and limitations */ -// Package fpe provides some encoding helpers for use +// Package fpeutils provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. -package fpe +package fpeutils import ( "fmt" diff --git a/numeral_test.go b/fpeutils/numeral_test.go similarity index 95% rename from numeral_test.go rename to fpeutils/numeral_test.go index 12510de..fdc5ab7 100644 --- a/numeral_test.go +++ b/fpeutils/numeral_test.go @@ -16,7 +16,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -package fpe + +// Package fpeutils provides some encoding helpers for use +// in the FF1 and FF3 format-preserving encryption packages. +package fpeutils import ( "fmt" From 7324c404c3dc4786f8d0402b882deaef8e5a8ebb Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 31 Dec 2018 18:57:55 +0000 Subject: [PATCH 27/29] rename to NewCipherWithAlphabet --- README.md | 2 +- ff1/ff1.go | 4 ++-- ff1/ff1_test.go | 4 ++-- ff3/ff3.go | 4 ++-- ff3/ff3_test.go | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a5d9014..d151a1e 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ func main() { // Create a new FF1 cipher "object" // Alphabet defines the supported character set, and 8 is the tweak length. - FF1, err := ff1.NewAlphaCipher(alphabet, 8, key, tweak) + FF1, err := ff1.NewCipherWithAlphabet(alphabet, 8, key, tweak) if err != nil { panic(err) } diff --git a/ff1/ff1.go b/ff1/ff1.go index 19168d5..51a4197 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -79,12 +79,12 @@ const ( // NewCipher is provided for backwards compatibility for old client code. func NewCipher(radix int, maxTLen int, key []byte, tweak []byte) (Cipher, error) { - return NewAlphaCipher(legacyAlphabet[:radix], maxTLen, key, tweak) + return NewCipherWithAlphabet(legacyAlphabet[:radix], maxTLen, key, tweak) } // NewAlphaCipher initializes a new FF1 Cipher for encryption or decryption use // based on the alphabet, max tweak length, key and tweak parameters. -func NewAlphaCipher(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { +func NewCipherWithAlphabet(alphabet string, maxTLen int, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) diff --git a/ff1/ff1_test.go b/ff1/ff1_test.go index 05b2601..927be7f 100644 --- a/ff1/ff1_test.go +++ b/ff1/ff1_test.go @@ -242,7 +242,7 @@ func TestUnicode(t *testing.T) { tweak, err := hex.DecodeString("D8E7920AFA330A73") // 0-9 plus a 1-byte, 2-byte, 3-byte and 4-byte utf-8 chars - ff1, err := NewAlphaCipher("0123456789\u0024\u00A2\u0939\u10348", 8, key, tweak) + ff1, err := NewCipherWithAlphabet("0123456789\u0024\u00A2\u0939\u10348", 8, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } @@ -279,7 +279,7 @@ func TestAlphabetSizes(t *testing.T) { t.Fatalf("TestAlphabetSizes: %s", err) } - ff1, err := NewAlphaCipher(a, 8, key, tweak) + ff1, err := NewCipherWithAlphabet(a, 8, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } diff --git a/ff3/ff3.go b/ff3/ff3.go index eb09255..2319625 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -68,12 +68,12 @@ const ( // NewCipher is provided for backwards compatibility for old client code. func NewCipher(radix int, key []byte, tweak []byte) (Cipher, error) { - return NewAlphaCipher(legacyAlphabet[:radix], key, tweak) + return NewCipherWithAlphabet(legacyAlphabet[:radix], key, tweak) } // NewAlphaCipher initializes a new FF3 Cipher for encryption or decryption use // based on the alphabet, max tweak length, key and tweak parameters. -func NewAlphaCipher(alphabet string, key []byte, tweak []byte) (Cipher, error) { +func NewCipherWithAlphabet(alphabet string, key []byte, tweak []byte) (Cipher, error) { var newCipher Cipher keyLen := len(key) diff --git a/ff3/ff3_test.go b/ff3/ff3_test.go index 1de2d33..de0b23f 100644 --- a/ff3/ff3_test.go +++ b/ff3/ff3_test.go @@ -222,7 +222,7 @@ func TestAlphabetSizes(t *testing.T) { t.Fatalf("TestAlphabetSizes: %s", err) } - ff3, err := NewAlphaCipher(a, key, tweak) + ff3, err := NewCipherWithAlphabet(a, key, tweak) if err != nil { t.Fatalf("Unable to create cipher: %v", err) } From a86969410d3516d6b635b4b628e0d4a784057d3a Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 31 Dec 2018 20:02:50 +0000 Subject: [PATCH 28/29] Decode - improved string building --- fpeutils/codec.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/fpeutils/codec.go b/fpeutils/codec.go index 1948556..8065aec 100644 --- a/fpeutils/codec.go +++ b/fpeutils/codec.go @@ -22,6 +22,7 @@ See the License for the specific language governing permissions and limitations package fpeutils import ( + "bytes" "fmt" "unicode/utf8" ) @@ -95,12 +96,13 @@ func (a *Codec) Encode(s string) ([]uint16, error) { // It is an error for the array to contain values outside the boundary of the // alphabet. func (a *Codec) Decode(n []uint16) (string, error) { - var ret string + var b bytes.Buffer + for i, v := range n { if v < 0 || int(v) > len(a.utr)-1 { - return ret, fmt.Errorf("numeral at position %d out of range: %d not in [0..%d]", i, v, len(a.utr)-1) + return "", fmt.Errorf("numeral at position %d out of range: %d not in [0..%d]", i, v, len(a.utr)-1) } - ret = ret + string(a.utr[v]) + b.WriteString(string(a.utr[v])) } - return ret, nil + return b.String(), nil } From e47cd888c49a26d373510b364885ce7c54cb63f3 Mon Sep 17 00:00:00 2001 From: Martin Waite Date: Mon, 31 Dec 2018 20:08:17 +0000 Subject: [PATCH 29/29] fpeUtils --- ff1/ff1.go | 18 +++++++++--------- ff3/ff3.go | 18 +++++++++--------- {fpeutils => fpeUtils}/codec.go | 4 ++-- {fpeutils => fpeUtils}/codec_test.go | 4 ++-- {fpeutils => fpeUtils}/numeral.go | 4 ++-- {fpeutils => fpeUtils}/numeral_test.go | 4 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) rename {fpeutils => fpeUtils}/codec.go (97%) rename {fpeutils => fpeUtils}/codec_test.go (97%) rename {fpeutils => fpeUtils}/numeral.go (98%) rename {fpeutils => fpeUtils}/numeral_test.go (97%) diff --git a/ff1/ff1.go b/ff1/ff1.go index 51a4197..477610c 100644 --- a/ff1/ff1.go +++ b/ff1/ff1.go @@ -27,7 +27,7 @@ import ( "encoding/binary" "errors" "fmt" - "github.com/capitalone/fpe/fpeutils" + "github.com/capitalone/fpe/fpeUtils" "math" "math/big" ) @@ -62,7 +62,7 @@ type cbcMode interface { // using a particular key, radix, and tweak type Cipher struct { tweak []byte - codec fpeutils.Codec + codec fpeUtils.Codec radix int minLen uint32 maxLen uint32 @@ -94,7 +94,7 @@ func NewCipherWithAlphabet(alphabet string, maxTLen int, key []byte, tweak []byt return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - codec, err := fpeutils.NewCodec(alphabet) + codec, err := fpeUtils.NewCodec(alphabet) if err != nil { return newCipher, fmt.Errorf("error making codec: %s", err) } @@ -280,12 +280,12 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpeutils.Num(A, uint64(radix)) + numA, err = fpeUtils.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpeutils.Num(B, uint64(radix)) + numB, err = fpeUtils.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -361,7 +361,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numB = numC } - return fpeutils.DecodeNum(&numA, len(A), &numB, len(B), c.codec) + return fpeUtils.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // Decrypt decrypts the string X over the current FF1 parameters @@ -505,12 +505,12 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numModV.Exp(&numRadix, &numV, nil) // Bootstrap for 1st round - numA, err = fpeutils.Num(A, uint64(radix)) + numA, err = fpeUtils.Num(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } - numB, err = fpeutils.Num(B, uint64(radix)) + numB, err = fpeUtils.Num(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -586,7 +586,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numA = numC } - return fpeutils.DecodeNum(&numA, len(A), &numB, len(B), c.codec) + return fpeUtils.DecodeNum(&numA, len(A), &numB, len(B), c.codec) } // ciph defines how the main block cipher is called. diff --git a/ff3/ff3.go b/ff3/ff3.go index 2319625..a72eb36 100644 --- a/ff3/ff3.go +++ b/ff3/ff3.go @@ -26,7 +26,7 @@ import ( "crypto/cipher" "errors" "fmt" - "github.com/capitalone/fpe/fpeutils" + "github.com/capitalone/fpe/fpeUtils" "math" "math/big" ) @@ -53,7 +53,7 @@ var ( // using a particular key, radix, and tweak type Cipher struct { tweak []byte - codec fpeutils.Codec + codec fpeUtils.Codec minLen uint32 maxLen uint32 @@ -83,7 +83,7 @@ func NewCipherWithAlphabet(alphabet string, key []byte, tweak []byte) (Cipher, e return newCipher, errors.New("key length must be 128, 192, or 256 bits") } - codec, err := fpeutils.NewCodec(alphabet) + codec, err := fpeUtils.NewCodec(alphabet) if err != nil { return newCipher, fmt.Errorf("error making codec: %s", err) } @@ -222,7 +222,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(B) with padding - numB, err = fpeutils.NumRev(B, uint64(radix)) + numB, err = fpeUtils.NumRev(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -247,7 +247,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { numY.SetBytes(S[:]) // Calculate c - numC, err = fpeutils.NumRev(A, uint64(radix)) + numC, err = fpeUtils.NumRev(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -261,7 +261,7 @@ func (c Cipher) EncryptWithTweak(X string, tweak []byte) (string, error) { } C = C[:m] - _, err := fpeutils.StrRev(&numC, C, uint64(c.codec.Radix())) + _, err := fpeUtils.StrRev(&numC, C, uint64(c.codec.Radix())) if err != nil { return "", err } @@ -381,7 +381,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { P[3] = W[3] ^ byte(i) // The remaining 12 bytes of P are for rev(A) with padding - numA, err = fpeutils.NumRev(A, uint64(radix)) + numA, err = fpeUtils.NumRev(A, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -406,7 +406,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { numY.SetBytes(S[:]) // Calculate c - numC, err = fpeutils.NumRev(B, uint64(radix)) + numC, err = fpeUtils.NumRev(B, uint64(radix)) if err != nil { return ret, ErrStringNotInRadix } @@ -420,7 +420,7 @@ func (c Cipher) DecryptWithTweak(X string, tweak []byte) (string, error) { } C = C[:m] - _, err := fpeutils.StrRev(&numC, C, uint64(c.codec.Radix())) + _, err := fpeUtils.StrRev(&numC, C, uint64(c.codec.Radix())) if err != nil { return "", err } diff --git a/fpeutils/codec.go b/fpeUtils/codec.go similarity index 97% rename from fpeutils/codec.go rename to fpeUtils/codec.go index 8065aec..96d3940 100644 --- a/fpeutils/codec.go +++ b/fpeUtils/codec.go @@ -17,9 +17,9 @@ See the License for the specific language governing permissions and limitations */ -// Package fpeutils provides some encoding helpers for use +// Package fpeUtils provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. -package fpeutils +package fpeUtils import ( "bytes" diff --git a/fpeutils/codec_test.go b/fpeUtils/codec_test.go similarity index 97% rename from fpeutils/codec_test.go rename to fpeUtils/codec_test.go index 34d2eb0..9c37a4a 100644 --- a/fpeutils/codec_test.go +++ b/fpeUtils/codec_test.go @@ -17,9 +17,9 @@ See the License for the specific language governing permissions and limitations */ -// Package fpeutils provides some encoding helpers for use +// Package fpeUtils provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. -package fpeutils +package fpeUtils import ( "bytes" diff --git a/fpeutils/numeral.go b/fpeUtils/numeral.go similarity index 98% rename from fpeutils/numeral.go rename to fpeUtils/numeral.go index 7c30f64..633e0c8 100644 --- a/fpeutils/numeral.go +++ b/fpeUtils/numeral.go @@ -17,9 +17,9 @@ See the License for the specific language governing permissions and limitations */ -// Package fpeutils provides some encoding helpers for use +// Package fpeUtils provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. -package fpeutils +package fpeUtils import ( "fmt" diff --git a/fpeutils/numeral_test.go b/fpeUtils/numeral_test.go similarity index 97% rename from fpeutils/numeral_test.go rename to fpeUtils/numeral_test.go index fdc5ab7..bc18c29 100644 --- a/fpeutils/numeral_test.go +++ b/fpeUtils/numeral_test.go @@ -17,9 +17,9 @@ See the License for the specific language governing permissions and limitations */ -// Package fpeutils provides some encoding helpers for use +// Package fpeUtils provides some encoding helpers for use // in the FF1 and FF3 format-preserving encryption packages. -package fpeutils +package fpeUtils import ( "fmt"