From 0b694f7b6733d30cf193468fb41cbab915a3c6a5 Mon Sep 17 00:00:00 2001 From: armfazh Date: Wed, 8 Feb 2023 13:09:32 -0800 Subject: [PATCH 1/7] Adding ASCON, an AEAD lightweight cipher. --- cipher/ascon/ascon.go | 260 + cipher/ascon/ascon_test.go | 141 + cipher/ascon/testdata/ascon128.json | 8714 ++++++++++++++++++++++++++ cipher/ascon/testdata/ascon128a.json | 8714 ++++++++++++++++++++++++++ 4 files changed, 17829 insertions(+) create mode 100644 cipher/ascon/ascon.go create mode 100644 cipher/ascon/ascon_test.go create mode 100644 cipher/ascon/testdata/ascon128.json create mode 100644 cipher/ascon/testdata/ascon128a.json diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go new file mode 100644 index 00000000..58b5acf3 --- /dev/null +++ b/cipher/ascon/ascon.go @@ -0,0 +1,260 @@ +// Package ascon provides a light-weight AEAD cipher. +// +// This packges implements the AEAD ciphers Ascon128 and Ascon128a as specified +// in https://ascon.iaik.tugraz.at/index.html +package ascon + +import ( + "bytes" + "crypto/cipher" + "encoding/binary" + "errors" + "math/bits" +) + +const ( + KeySize = 16 + NonceSize = 16 + TagSize = KeySize +) + +type Mode int + +const ( + Ascon128 Mode = iota + 1 + Ascon128a +) + +const ( + permA = 12 + permB = 6 // 6 for Ascon128, or 8 for Ascon128a + blockSize = 8 // 8 for Ascon128, or 16 for Ascon128a + ivSize = 8 + stateSize = ivSize + KeySize + NonceSize +) + +var ( + roundConst = [12]uint64{0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b} + subs = [32]int{ + 0x04, 0x0b, 0x1f, 0x14, 0x1a, 0x15, 0x09, 0x02, + 0x1b, 0x05, 0x08, 0x12, 0x1d, 0x03, 0x06, 0x1c, + 0x1e, 0x13, 0x07, 0x0e, 0x00, 0x0d, 0x11, 0x18, + 0x10, 0x0c, 0x01, 0x19, 0x16, 0x0a, 0x0f, 0x17, + } +) + +type Cipher struct { + state [stateSize]byte + key [KeySize]byte + mode Mode +} + +// New returns a Cipher struct implementing the cipher.AEAD interface. Mode is +// one of Ascon128 or Ascon128a. +func New(key []byte, m Mode) (*Cipher, error) { + if len(key) != KeySize { + return nil, ErrKeySize + } + if !(m == Ascon128 || m == Ascon128a) { + return nil, ErrMode + } + c := new(Cipher) + c.mode = m + copy(c.key[:], key) + var _ cipher.AEAD = c + return c, nil +} + +// NonceSize returns the size of the nonce that must be passed to Seal +// and Open. +func (a *Cipher) NonceSize() int { return NonceSize } + +// Overhead returns the maximum difference between the lengths of a +// plaintext and its ciphertext. +func (a *Cipher) Overhead() int { return TagSize } + +// Seal encrypts and authenticates plaintext, authenticates the +// additional data and appends the result to dst, returning the updated +// slice. The nonce must be NonceSize() bytes long and unique for all +// time, for a given key. +// +// To reuse plaintext's storage for the encrypted output, use plaintext[:0] +// as dst. Otherwise, the remaining capacity of dst must not overlap plaintext. +func (a *Cipher) Seal(dst, nonce, plaintext, additionalData []byte) []byte { + if len(nonce) != NonceSize { + panic(ErrNonceSize) + } + + ptLen := len(plaintext) + output := make([]byte, ptLen+TagSize) + ciphertext, tag := output[:ptLen], output[ptLen:] + + a.initialize(nonce) + a.assocData(additionalData) + a.procText(plaintext, ciphertext, true) + a.finalize(tag) + + return output +} + +// Open decrypts and authenticates ciphertext, authenticates the +// additional data and, if successful, appends the resulting plaintext +// to dst, returning the updated slice. The nonce must be NonceSize() +// bytes long and both it and the additional data must match the +// value passed to Seal. +// +// To reuse ciphertext's storage for the decrypted output, use ciphertext[:0] +// as dst. Otherwise, the remaining capacity of dst must not overlap plaintext. +// +// Even if the function fails, the contents of dst, up to its capacity, +// may be overwritten. +func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { + if len(nonce) != NonceSize { + panic(ErrNonceSize) + } + + ptLen := len(ciphertext) - TagSize + plaintext := make([]byte, ptLen) + ciphertext, tag0 := ciphertext[:ptLen], ciphertext[ptLen:] + tag1 := (&[TagSize]byte{})[:] + + a.initialize(nonce) + a.assocData(additionalData) + a.procText(ciphertext, plaintext, false) + a.finalize(tag1) + + if !bytes.Equal(tag0, tag1) { + return nil, ErrDecryption + } + + return plaintext, nil +} + +func (a *Cipher) initialize(nonce []byte) { + bcs := blockSize * byte(a.mode) + pB := permB + 2*(byte(a.mode)-1) + a.state[0] = KeySize * 8 + a.state[1] = bcs * 8 + a.state[2] = permA + a.state[3] = pB + a.state[4] = 0 + a.state[5] = 0 + a.state[6] = 0 + a.state[7] = 0 + copy(a.state[ivSize:ivSize+KeySize], a.key[:]) + copy(a.state[ivSize+KeySize:ivSize+KeySize+NonceSize], nonce) + a.perm(permA) + + for i := 0; i < KeySize; i++ { + a.state[stateSize-KeySize+i] ^= a.key[i] + } +} + +func (a *Cipher) assocData(add []byte) { + bcs := blockSize * int(a.mode) + pB := permB + 2*(int(a.mode)-1) + if len(add) > 0 { + for ; len(add) >= bcs; add = add[bcs:] { + for i := 0; i < bcs; i++ { + a.state[i] ^= add[i] + } + a.perm(pB) + } + if len(add) >= 0 { + for i := 0; i < len(add); i++ { + a.state[i] ^= add[i] + } + a.state[len(add)] ^= 0x80 + a.perm(pB) + } + } + a.state[stateSize-1] ^= 0x01 +} + +func (a *Cipher) procText(in, out []byte, enc bool) { + bcs := blockSize * int(a.mode) + pB := permB + 2*(int(a.mode)-1) + cc := in + if enc { + cc = out + } + for ; len(in) >= bcs; in, out, cc = in[bcs:], out[bcs:], cc[bcs:] { + for i := 0; i < bcs; i++ { + out[i] = a.state[i] ^ in[i] + a.state[i] = cc[i] + } + a.perm(pB) + } + if len(in) >= 0 { + for i := 0; i < len(in); i++ { + out[i] = a.state[i] ^ in[i] + a.state[i] = cc[i] + } + a.state[len(in)] ^= 0x80 + } +} + +func (a *Cipher) finalize(tag []byte) { + bcs := blockSize * int(a.mode) + for i := 0; i < KeySize; i++ { + a.state[bcs+i] ^= a.key[i] + } + a.perm(permA) + for i := 0; i < KeySize; i++ { + tag[i] = a.state[stateSize-KeySize+i] ^ a.key[i] + } +} + +func (a *Cipher) perm(n int) { + x := [5]uint64{} + x[0] = binary.BigEndian.Uint64(a.state[0:8]) + x[1] = binary.BigEndian.Uint64(a.state[8:16]) + x[2] = binary.BigEndian.Uint64(a.state[16:24]) + x[3] = binary.BigEndian.Uint64(a.state[24:32]) + x[4] = binary.BigEndian.Uint64(a.state[32:40]) + + for i := 0; i < n; i++ { + // pC -- addition of constants + ri := i + if n != permA { + ri = i + permA - n + } + x[2] ^= roundConst[ri] + + // pS -- substitution layer + for j := 0; j < 64; j++ { + sx := subs[0| + (((x[0]>>j)&0x1)<<4)| + (((x[1]>>j)&0x1)<<3)| + (((x[2]>>j)&0x1)<<2)| + (((x[3]>>j)&0x1)<<1)| + (((x[4]>>j)&0x1)<<0)] + mask := uint64(1) << j + x[0] = (x[0] &^ mask) | uint64((sx>>4)&0x1)<>3)&0x1)<>2)&0x1)<>1)&0x1)<>0)&0x1)< Date: Thu, 9 Feb 2023 13:27:18 -0800 Subject: [PATCH 2/7] Adding a 64-bit oriented implementation for ascon. --- cipher/ascon/ascon.go | 113 ++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 66 deletions(-) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index 58b5acf3..cb16154e 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -44,9 +44,9 @@ var ( ) type Cipher struct { - state [stateSize]byte - key [KeySize]byte - mode Mode + s [5]uint64 + key [2]uint64 + mode Mode } // New returns a Cipher struct implementing the cipher.AEAD interface. Mode is @@ -60,7 +60,8 @@ func New(key []byte, m Mode) (*Cipher, error) { } c := new(Cipher) c.mode = m - copy(c.key[:], key) + c.key[0] = binary.BigEndian.Uint64(key[0:8]) + c.key[1] = binary.BigEndian.Uint64(key[8:16]) var _ cipher.AEAD = c return c, nil } @@ -131,23 +132,16 @@ func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er } func (a *Cipher) initialize(nonce []byte) { - bcs := blockSize * byte(a.mode) - pB := permB + 2*(byte(a.mode)-1) - a.state[0] = KeySize * 8 - a.state[1] = bcs * 8 - a.state[2] = permA - a.state[3] = pB - a.state[4] = 0 - a.state[5] = 0 - a.state[6] = 0 - a.state[7] = 0 - copy(a.state[ivSize:ivSize+KeySize], a.key[:]) - copy(a.state[ivSize+KeySize:ivSize+KeySize+NonceSize], nonce) + bcs := blockSize * uint64(a.mode) + pB := permB + 2*(uint64(a.mode)-1) + a.s[0] = ((KeySize * 8) << 56) | ((bcs * 8) << 48) | (permA << 40) | (pB << 32) + a.s[1] = a.key[0] + a.s[2] = a.key[1] + a.s[3] = binary.BigEndian.Uint64(nonce[0:8]) + a.s[4] = binary.BigEndian.Uint64(nonce[8:16]) a.perm(permA) - - for i := 0; i < KeySize; i++ { - a.state[stateSize-KeySize+i] ^= a.key[i] - } + a.s[3] ^= a.key[0] + a.s[4] ^= a.key[1] } func (a *Cipher) assocData(add []byte) { @@ -155,20 +149,20 @@ func (a *Cipher) assocData(add []byte) { pB := permB + 2*(int(a.mode)-1) if len(add) > 0 { for ; len(add) >= bcs; add = add[bcs:] { - for i := 0; i < bcs; i++ { - a.state[i] ^= add[i] + for i := 0; i < bcs; i += 8 { + a.s[i/8] ^= binary.BigEndian.Uint64(add[i : i+8]) } a.perm(pB) } if len(add) >= 0 { for i := 0; i < len(add); i++ { - a.state[i] ^= add[i] + a.s[i/8] ^= uint64(add[i]) << (56 - 8*(i%8)) } - a.state[len(add)] ^= 0x80 + a.s[len(add)/8] ^= uint64(0x80) << (56 - 8*(len(add)%8)) a.perm(pB) } } - a.state[stateSize-1] ^= 0x01 + a.s[4] ^= 0x01 } func (a *Cipher) procText(in, out []byte, enc bool) { @@ -179,77 +173,64 @@ func (a *Cipher) procText(in, out []byte, enc bool) { cc = out } for ; len(in) >= bcs; in, out, cc = in[bcs:], out[bcs:], cc[bcs:] { - for i := 0; i < bcs; i++ { - out[i] = a.state[i] ^ in[i] - a.state[i] = cc[i] + for i := 0; i < bcs; i += 8 { + binary.BigEndian.PutUint64(out[i:i+8], a.s[i/8]^binary.BigEndian.Uint64(in[i:i+8])) + a.s[i/8] = binary.BigEndian.Uint64(cc[i : i+8]) } a.perm(pB) } if len(in) >= 0 { for i := 0; i < len(in); i++ { - out[i] = a.state[i] ^ in[i] - a.state[i] = cc[i] + off := 56 - (8 * (i % 8)) + si := byte((a.s[i/8] >> off) & 0xFF) + out[i] = si ^ in[i] + a.s[i/8] = (uint64(a.s[i/8]) &^ (0xFF << off)) | uint64(cc[i])<>j)&0x1)<<4)| - (((x[1]>>j)&0x1)<<3)| - (((x[2]>>j)&0x1)<<2)| - (((x[3]>>j)&0x1)<<1)| - (((x[4]>>j)&0x1)<<0)] + (((a.s[0]>>j)&0x1)<<4)| + (((a.s[1]>>j)&0x1)<<3)| + (((a.s[2]>>j)&0x1)<<2)| + (((a.s[3]>>j)&0x1)<<1)| + (((a.s[4]>>j)&0x1)<<0)] mask := uint64(1) << j - x[0] = (x[0] &^ mask) | uint64((sx>>4)&0x1)<>3)&0x1)<>2)&0x1)<>1)&0x1)<>0)&0x1)<>4)&0x1)<>3)&0x1)<>2)&0x1)<>1)&0x1)<>0)&0x1)< Date: Thu, 9 Feb 2023 15:50:33 -0800 Subject: [PATCH 3/7] Applying review comments and more testing for Ascon. --- cipher/ascon/ascon.go | 23 ++++-- cipher/ascon/ascon_test.go | 79 +++++++++++++------ .../testdata/{ascon128.json => Ascon128.json} | 0 .../{ascon128a.json => Ascon128a.json} | 0 4 files changed, 71 insertions(+), 31 deletions(-) rename cipher/ascon/testdata/{ascon128.json => Ascon128.json} (100%) rename cipher/ascon/testdata/{ascon128a.json => Ascon128a.json} (100%) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index cb16154e..40c0fc85 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -1,12 +1,11 @@ // Package ascon provides a light-weight AEAD cipher. // -// This packges implements the AEAD ciphers Ascon128 and Ascon128a as specified +// This package implements Ascon128 and Ascon128a two AEAD ciphers as specified // in https://ascon.iaik.tugraz.at/index.html package ascon import ( "bytes" - "crypto/cipher" "encoding/binary" "errors" "math/bits" @@ -20,6 +19,17 @@ const ( type Mode int +func (m Mode) String() string { + switch m { + case Ascon128: + return "Ascon128" + case Ascon128a: + return "Ascon128a" + default: + panic(ErrMode) + } +} + const ( Ascon128 Mode = iota + 1 Ascon128a @@ -27,7 +37,7 @@ const ( const ( permA = 12 - permB = 6 // 6 for Ascon128, or 8 for Ascon128a + permB = 6 // 6 for Ascon128, or 8 for Ascon128a blockSize = 8 // 8 for Ascon128, or 16 for Ascon128a ivSize = 8 stateSize = ivSize + KeySize + NonceSize @@ -62,7 +72,7 @@ func New(key []byte, m Mode) (*Cipher, error) { c.mode = m c.key[0] = binary.BigEndian.Uint64(key[0:8]) c.key[1] = binary.BigEndian.Uint64(key[8:16]) - var _ cipher.AEAD = c + return c, nil } @@ -113,6 +123,9 @@ func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er if len(nonce) != NonceSize { panic(ErrNonceSize) } + if len(ciphertext) < TagSize { + return nil, ErrDecryption + } ptLen := len(ciphertext) - TagSize plaintext := make([]byte, ptLen) @@ -184,7 +197,7 @@ func (a *Cipher) procText(in, out []byte, enc bool) { off := 56 - (8 * (i % 8)) si := byte((a.s[i/8] >> off) & 0xFF) out[i] = si ^ in[i] - a.s[i/8] = (uint64(a.s[i/8]) &^ (0xFF << off)) | uint64(cc[i])< Date: Tue, 14 Feb 2023 06:16:19 -0800 Subject: [PATCH 4/7] Replaces substitution layer by 64-bit operations. --- cipher/ascon/ascon.go | 68 +++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index 40c0fc85..b4aebe85 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -43,16 +43,6 @@ const ( stateSize = ivSize + KeySize + NonceSize ) -var ( - roundConst = [12]uint64{0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b} - subs = [32]int{ - 0x04, 0x0b, 0x1f, 0x14, 0x1a, 0x15, 0x09, 0x02, - 0x1b, 0x05, 0x08, 0x12, 0x1d, 0x03, 0x06, 0x1c, - 0x1e, 0x13, 0x07, 0x0e, 0x00, 0x0d, 0x11, 0x18, - 0x10, 0x0c, 0x01, 0x19, 0x16, 0x0a, 0x0f, 0x17, - } -) - type Cipher struct { s [5]uint64 key [2]uint64 @@ -212,38 +202,48 @@ func (a *Cipher) finalize(tag []byte) { binary.BigEndian.PutUint64(tag[8:16], a.s[4]^a.key[1]) } +var roundConst = [12]uint64{0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b} + func (a *Cipher) perm(n int) { + ri := 0 + if n != permA { + ri = permA - n + } + + x0, x1, x2, x3, x4 := a.s[0], a.s[1], a.s[2], a.s[3], a.s[4] for i := 0; i < n; i++ { // pC -- addition of constants - ri := i - if n != permA { - ri = i + permA - n - } - a.s[2] ^= roundConst[ri] + x2 ^= roundConst[ri+i] // pS -- substitution layer - for j := 0; j < 64; j++ { - sx := subs[0| - (((a.s[0]>>j)&0x1)<<4)| - (((a.s[1]>>j)&0x1)<<3)| - (((a.s[2]>>j)&0x1)<<2)| - (((a.s[3]>>j)&0x1)<<1)| - (((a.s[4]>>j)&0x1)<<0)] - mask := uint64(1) << j - a.s[0] = (a.s[0] &^ mask) | uint64((sx>>4)&0x1)<>3)&0x1)<>2)&0x1)<>1)&0x1)<>0)&0x1)< Date: Tue, 14 Feb 2023 06:28:05 -0800 Subject: [PATCH 5/7] Add constant time compare. --- cipher/ascon/ascon.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index b4aebe85..4ff41041 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -5,7 +5,7 @@ package ascon import ( - "bytes" + "crypto/subtle" "encoding/binary" "errors" "math/bits" @@ -127,7 +127,7 @@ func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er a.procText(ciphertext, plaintext, false) a.finalize(tag1) - if !bytes.Equal(tag0, tag1) { + if subtle.ConstantTimeCompare(tag0, tag1) == 0 { return nil, ErrDecryption } From 8e1e716c5275eb13a4a259d050e16404fa7d2cf3 Mon Sep 17 00:00:00 2001 From: armfazh Date: Tue, 14 Feb 2023 07:03:30 -0800 Subject: [PATCH 6/7] Repacing third slice by conditional execution. --- cipher/ascon/ascon.go | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index 4ff41041..57975b24 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -1,7 +1,8 @@ // Package ascon provides a light-weight AEAD cipher. // // This package implements Ascon128 and Ascon128a two AEAD ciphers as specified -// in https://ascon.iaik.tugraz.at/index.html +// in ASCON v1.2 by C. Dobraunig, M. Eichlseder, F. Mendel, M. Schläffer. +// https://ascon.iaik.tugraz.at/index.html package ascon import ( @@ -171,23 +172,29 @@ func (a *Cipher) assocData(add []byte) { func (a *Cipher) procText(in, out []byte, enc bool) { bcs := blockSize * int(a.mode) pB := permB + 2*(int(a.mode)-1) - cc := in + mask := uint64(0) if enc { - cc = out + mask -= 1 } - for ; len(in) >= bcs; in, out, cc = in[bcs:], out[bcs:], cc[bcs:] { + + for ; len(in) >= bcs; in, out = in[bcs:], out[bcs:] { for i := 0; i < bcs; i += 8 { - binary.BigEndian.PutUint64(out[i:i+8], a.s[i/8]^binary.BigEndian.Uint64(in[i:i+8])) - a.s[i/8] = binary.BigEndian.Uint64(cc[i : i+8]) + inW := binary.BigEndian.Uint64(in[i : i+8]) + outW := a.s[i/8] ^ inW + binary.BigEndian.PutUint64(out[i:i+8], outW) + + a.s[i/8] = (inW &^ mask) | (outW & mask) } a.perm(pB) } if len(in) >= 0 { + mask8 := byte(mask & 0xFF) for i := 0; i < len(in); i++ { off := 56 - (8 * (i % 8)) si := byte((a.s[i/8] >> off) & 0xFF) out[i] = si ^ in[i] - a.s[i/8] = (a.s[i/8] &^ (0xFF << off)) | uint64(cc[i])< Date: Thu, 16 Feb 2023 09:02:32 -0800 Subject: [PATCH 7/7] Remove unnecessary if condition. --- cipher/ascon/ascon.go | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index 57975b24..d4dd03d2 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -158,13 +158,11 @@ func (a *Cipher) assocData(add []byte) { } a.perm(pB) } - if len(add) >= 0 { - for i := 0; i < len(add); i++ { - a.s[i/8] ^= uint64(add[i]) << (56 - 8*(i%8)) - } - a.s[len(add)/8] ^= uint64(0x80) << (56 - 8*(len(add)%8)) - a.perm(pB) + for i := 0; i < len(add); i++ { + a.s[i/8] ^= uint64(add[i]) << (56 - 8*(i%8)) } + a.s[len(add)/8] ^= uint64(0x80) << (56 - 8*(len(add)%8)) + a.perm(pB) } a.s[4] ^= 0x01 } @@ -187,17 +185,16 @@ func (a *Cipher) procText(in, out []byte, enc bool) { } a.perm(pB) } - if len(in) >= 0 { - mask8 := byte(mask & 0xFF) - for i := 0; i < len(in); i++ { - off := 56 - (8 * (i % 8)) - si := byte((a.s[i/8] >> off) & 0xFF) - out[i] = si ^ in[i] - ss := (in[i] &^ mask8) | (out[i] & mask8) - a.s[i/8] = (a.s[i/8] &^ (0xFF << off)) | uint64(ss)<> off) & 0xFF) + out[i] = si ^ in[i] + ss := (in[i] &^ mask8) | (out[i] & mask8) + a.s[i/8] = (a.s[i/8] &^ (0xFF << off)) | uint64(ss)<