From b547cfe25b8bb63ea84685417896288184795293 Mon Sep 17 00:00:00 2001 From: armfazh Date: Mon, 20 Feb 2023 11:59:28 -0800 Subject: [PATCH 1/2] Updating formulas for constants. --- cipher/ascon/ascon.go | 123 ++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 70 deletions(-) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index 150d4fd6..2164eeea 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -1,8 +1,14 @@ -// Package ascon provides a light-weight AEAD cipher. +// Package ascon provides ASCON family of light-weight AEAD ciphers. // // This package implements Ascon128 and Ascon128a two AEAD ciphers as specified // in ASCON v1.2 by C. Dobraunig, M. Eichlseder, F. Mendel, M. Schläffer. // https://ascon.iaik.tugraz.at/index.html +// +// It also implements Ascon-80pq, which has an increased key-size to provide +// more resistance against a quantum adversary using Grover’s algorithm for +// key search. Since Ascon-128 and Ascon-80pq share the same building blocks +// and same parameters except the size of the key, it is claimed the same +// security for Ascon-80pq against classical attacks as for Ascon-128. package ascon import ( @@ -13,14 +19,16 @@ import ( ) const ( - KeySize = 16 - KeySize80pq = 20 + KeySize = 16 // For Ascon128 and Ascon128a. + KeySize80pq = 20 // Only for Ascon80pq. NonceSize = 16 - TagSize = KeySize + TagSize = 16 ) type Mode int +// KeySize is 16 for Ascon128 and Ascon128a, or 20 for Ascon80pq. +func (m Mode) KeySize() int { v := int(m) >> 2; return KeySize&^v | KeySize80pq&v } func (m Mode) String() string { switch m { case Ascon128: @@ -35,22 +43,12 @@ func (m Mode) String() string { } const ( - Ascon128 Mode = iota + 1 - Ascon128a - // Ascon-80pq has an increased key-size to provide more resistance against a quantum - // adversary using Grover’s algorithm for key search. Since Ascon-128 and Ascon- - // 80pq share the same building blocks and same parameters except the size of the key, - // we claim the same security for Ascon-80pq against classical attacks as for Ascon-128. - Ascon80pq + Ascon128 Mode = 1 + Ascon128a Mode = 2 + Ascon80pq Mode = -1 ) -const ( - permA = 12 - permB = 6 // 6 for Ascon128 and Ascon80pq, or 8 for Ascon128a - blockSize = 8 // 8 for Ascon128 and Ascon80pq, or 16 for Ascon128a - ivSize = 8 - stateSize = ivSize + KeySize + NonceSize -) +const permA = 12 type Cipher struct { s [5]uint64 @@ -58,8 +56,9 @@ type Cipher struct { mode Mode } -// New returns a Cipher struct implementing the cipher.AEAD interface. Mode is -// one of Ascon128, Ascon128a or Ascon80pq. +// New returns a Cipher struct implementing the crypto/cipher.AEAD interface. +// The key must be Mode.KeySize() bytes long, and the mode is one of Ascon128, +// Ascon128a or Ascon80pq. func New(key []byte, m Mode) (*Cipher, error) { if (m == Ascon128 || m == Ascon128a) && len(key) != KeySize { return nil, ErrKeySize @@ -73,15 +72,15 @@ func New(key []byte, m Mode) (*Cipher, error) { c := new(Cipher) c.mode = m if m == Ascon80pq { - c.key[0] = (uint64)(binary.BigEndian.Uint32(key[0:4])) + c.key[0] = uint64(binary.BigEndian.Uint32(key[0:4])) c.key[1] = binary.BigEndian.Uint64(key[4:12]) c.key[2] = binary.BigEndian.Uint64(key[12:20]) - return c, nil + } else { + c.key[0] = 0 + c.key[1] = binary.BigEndian.Uint64(key[0:8]) + c.key[2] = binary.BigEndian.Uint64(key[8:16]) } - c.key[0] = binary.BigEndian.Uint64(key[0:8]) - c.key[1] = binary.BigEndian.Uint64(key[8:16]) - return c, nil } @@ -153,43 +152,35 @@ func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er return plaintext, nil } +func abs(x int) int { m := uint(x >> (bits.UintSize - 1)); return int((uint(x) + m) ^ m) } + +// blockSize = 8 for Ascon128 and Ascon80pq, or 16 for Ascon128a. +func (a *Cipher) blockSize() int { return abs(int(a.mode)) << 3 } + +// permB = 6 for Ascon128 and Ascon80pq, or 8 for Ascon128a. +func (a *Cipher) permB() int { return (abs(int(a.mode)) + 2) << 1 } + func (a *Cipher) initialize(nonce []byte) { - if a.mode == Ascon80pq { - bcs := blockSize * uint64(Ascon128) - pB := permB + 2*(uint64(Ascon128)-1) - a.s[0] = ((KeySize80pq * 8) << 56) | ((bcs * 8) << 48) | (permA << 40) | (pB << 32) | a.key[0] - a.s[1] = a.key[1] - a.s[2] = a.key[2] - } else { - 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] - } + bcs := uint64(a.blockSize()) + pB := uint64(a.permB()) + kS := uint64(a.mode.KeySize()) + a.s[0] = ((kS * 8) << 56) | ((bcs * 8) << 48) | (permA << 40) | (pB << 32) | a.key[0] + a.s[1] = a.key[1] + a.s[2] = a.key[2] a.s[3] = binary.BigEndian.Uint64(nonce[0:8]) a.s[4] = binary.BigEndian.Uint64(nonce[8:16]) + a.perm(permA) - if a.mode == Ascon80pq { - a.s[2] ^= a.key[0] - a.s[3] ^= a.key[1] - a.s[4] ^= a.key[2] - } else { - a.s[3] ^= a.key[0] - a.s[4] ^= a.key[1] - } + a.s[2] ^= a.key[0] + a.s[3] ^= a.key[1] + a.s[4] ^= a.key[2] } func (a *Cipher) assocData(add []byte) { - m := a.mode - if m == Ascon80pq { - m = Ascon128 - } - - bcs := blockSize * int(m) - pB := permB + 2*(int(m)-1) + bcs := a.blockSize() + pB := a.permB() if len(add) > 0 { for ; len(add) >= bcs; add = add[bcs:] { for i := 0; i < bcs; i += 8 { @@ -207,13 +198,8 @@ func (a *Cipher) assocData(add []byte) { } func (a *Cipher) procText(in, out []byte, enc bool) { - m := a.mode - if m == Ascon80pq { - m = Ascon128 - } - - bcs := blockSize * int(m) - pB := permB + 2*(int(m)-1) + bcs := a.blockSize() + pB := a.permB() mask := uint64(0) if enc { mask -= 1 @@ -242,22 +228,19 @@ func (a *Cipher) procText(in, out []byte, enc bool) { } func (a *Cipher) finalize(tag []byte) { + bcs := a.blockSize() if a.mode == Ascon80pq { - bcs := blockSize * int(Ascon128) a.s[bcs/8+0] ^= a.key[0]<<32 | a.key[1]>>32 a.s[bcs/8+1] ^= a.key[1]<<32 | a.key[2]>>32 a.s[bcs/8+2] ^= a.key[2] << 32 - a.perm(permA) - binary.BigEndian.PutUint64(tag[0:8], a.s[3]^a.key[1]) - binary.BigEndian.PutUint64(tag[8:16], a.s[4]^a.key[2]) } else { - bcs := blockSize * int(a.mode) - a.s[bcs/8+0] ^= a.key[0] - a.s[bcs/8+1] ^= a.key[1] - a.perm(permA) - binary.BigEndian.PutUint64(tag[0:8], a.s[3]^a.key[0]) - binary.BigEndian.PutUint64(tag[8:16], a.s[4]^a.key[1]) + a.s[bcs/8+0] ^= a.key[1] + a.s[bcs/8+1] ^= a.key[2] } + + a.perm(permA) + binary.BigEndian.PutUint64(tag[0:8], a.s[3]^a.key[1]) + binary.BigEndian.PutUint64(tag[8:16], a.s[4]^a.key[2]) } var roundConst = [12]uint64{0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87, 0x78, 0x69, 0x5a, 0x4b} From 85268174dfce1e85e88bcc9adc3f59cf53a812b3 Mon Sep 17 00:00:00 2001 From: armfazh Date: Tue, 21 Feb 2023 02:11:19 -0800 Subject: [PATCH 2/2] Allow concatenating and reuse payloads. --- cipher/ascon/ascon.go | 43 +++++++++++++++++++++++------ cipher/ascon/ascon_test.go | 56 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/cipher/ascon/ascon.go b/cipher/ascon/ascon.go index 2164eeea..62648f0c 100644 --- a/cipher/ascon/ascon.go +++ b/cipher/ascon/ascon.go @@ -28,7 +28,16 @@ const ( type Mode int // KeySize is 16 for Ascon128 and Ascon128a, or 20 for Ascon80pq. -func (m Mode) KeySize() int { v := int(m) >> 2; return KeySize&^v | KeySize80pq&v } +func (m Mode) KeySize() int { + switch m { + case Ascon128, Ascon128a, Ascon80pq: + v := int(m) >> 2 + return KeySize&^v | KeySize80pq&v + default: + panic(ErrMode) + } +} + func (m Mode) String() string { switch m { case Ascon128: @@ -105,15 +114,15 @@ func (a *Cipher) Seal(dst, nonce, plaintext, additionalData []byte) []byte { } ptLen := len(plaintext) - output := make([]byte, ptLen+TagSize) - ciphertext, tag := output[:ptLen], output[ptLen:] + ret, out := sliceForAppend(dst, ptLen+TagSize) + ciphertext, tag := out[:ptLen], out[ptLen:] a.initialize(nonce) a.assocData(additionalData) a.procText(plaintext, ciphertext, true) a.finalize(tag) - return output + return ret } // Open decrypts and authenticates ciphertext, authenticates the @@ -136,7 +145,8 @@ func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er } ptLen := len(ciphertext) - TagSize - plaintext := make([]byte, ptLen) + ret, out := sliceForAppend(dst, ptLen) + plaintext := out[:ptLen] ciphertext, tag0 := ciphertext[:ptLen], ciphertext[ptLen:] tag1 := (&[TagSize]byte{})[:] @@ -149,7 +159,7 @@ func (a *Cipher) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, er return nil, ErrDecryption } - return plaintext, nil + return ret, nil } func abs(x int) int { m := uint(x >> (bits.UintSize - 1)); return int((uint(x) + m) ^ m) } @@ -220,8 +230,10 @@ func (a *Cipher) procText(in, out []byte, enc bool) { 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) + inB := in[i] + outB := si ^ inB + out[i] = outB + ss := inB&^mask8 | outB&mask8 a.s[i/8] = (a.s[i/8] &^ (0xFF << off)) | uint64(ss)<= total { + head = in[:total] + } else { + head = make([]byte, total) + copy(head, in) + } + tail = head[len(in):] + return +} + var ( ErrKeySize = errors.New("ascon: bad key size") ErrNonceSize = errors.New("ascon: bad nonce size") diff --git a/cipher/ascon/ascon_test.go b/cipher/ascon/ascon_test.go index 04e6c046..80a8e049 100644 --- a/cipher/ascon/ascon_test.go +++ b/cipher/ascon/ascon_test.go @@ -127,6 +127,62 @@ func TestBadInputs(t *testing.T) { test.CheckIsErr(t, err, "should panic due to bad ciphertext") } +func TestAPI(t *testing.T) { + key, _ := hexa.DecodeString("000102030405060708090A0B0C0D0E0F") + nonce, _ := hexa.DecodeString("000102030405060708090A0B0C0D0E0F") + c, _ := ascon.New(key, ascon.Ascon128) + pt := []byte("helloworld") + ct, _ := hexa.DecodeString("d4e663d29cd60a693c20f890982e167d266f940b93b586945065") + + t.Run("append", func(t *testing.T) { + prefix := [5]byte{0x1F, 0x2F, 0x3F, 0x4F, 0x5F} + prefixAndCt := c.Seal(prefix[:], nonce, pt, nil) + got := prefixAndCt + want := append(append([]byte{}, prefix[:]...), ct...) + if !bytes.Equal(got, want) { + test.ReportError(t, got, want) + } + + ciphertext := prefixAndCt[len(prefix):] + prefix = [5]byte{0x11, 0x22, 0x33, 0x44, 0x55} + prefixAndPt, err := c.Open(prefix[:], nonce, ciphertext, nil) + if err != nil { + t.Fatal(err) + } + got = prefixAndPt + want = append(append([]byte{}, prefix[:]...), pt...) + if !bytes.Equal(got, want) { + test.ReportError(t, got, want) + } + }) + t.Run("reuse", func(t *testing.T) { + ptWithCap := make([]byte, len(pt), len(pt)+100) + copy(ptWithCap, pt) + // reusing the input to store the ciphertext. + ciphertext := c.Seal(ptWithCap[:0], nonce, ptWithCap, nil) + got := ciphertext + want := ct + if !bytes.Equal(got, want) { + test.ReportError(t, got, want) + } + test.CheckOk(&ptWithCap[0] == &ciphertext[0], "should have same address", t) + + ctWithCap := make([]byte, len(ct), len(ct)+100) + copy(ctWithCap, ct) + // reusing the input to store the plaintext. + plaintext, err := c.Open(ctWithCap[:0], nonce, ctWithCap, nil) + if err != nil { + t.Fatal(err) + } + got = plaintext + want = pt + if !bytes.Equal(got, want) { + test.ReportError(t, got, want) + } + test.CheckOk(&ctWithCap[0] == &plaintext[0], "should have same address", t) + }) +} + func BenchmarkAscon(b *testing.B) { for _, mode := range []ascon.Mode{ascon.Ascon128, ascon.Ascon128a, ascon.Ascon80pq} { for _, length := range []int{64, 1350, 8 * 1024} {