-
Notifications
You must be signed in to change notification settings - Fork 174
Adding Ascon, an AEAD lightweight cipher. #400
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
0b694f7
Adding ASCON, an AEAD lightweight cipher.
armfazh 1464732
Adding a 64-bit oriented implementation for ascon.
armfazh 0ea6c72
Applying review comments and more testing for Ascon.
armfazh 95f123a
Replaces substitution layer by 64-bit operations.
armfazh b4488bd
Add constant time compare.
armfazh 8e1e716
Repacing third slice by conditional execution.
armfazh 77d1862
Remove unnecessary if condition.
armfazh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
// Package ascon provides a light-weight AEAD cipher. | ||
// | ||
// 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 | ||
package ascon | ||
|
||
import ( | ||
"crypto/subtle" | ||
"encoding/binary" | ||
"errors" | ||
"math/bits" | ||
) | ||
|
||
const ( | ||
KeySize = 16 | ||
NonceSize = 16 | ||
TagSize = KeySize | ||
) | ||
|
||
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 | ||
) | ||
|
||
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 | ||
) | ||
|
||
type Cipher struct { | ||
s [5]uint64 | ||
key [2]uint64 | ||
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 | ||
c.key[0] = binary.BigEndian.Uint64(key[0:8]) | ||
c.key[1] = binary.BigEndian.Uint64(key[8:16]) | ||
|
||
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) | ||
} | ||
if len(ciphertext) < TagSize { | ||
return nil, ErrDecryption | ||
} | ||
|
||
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 subtle.ConstantTimeCompare(tag0, tag1) == 0 { | ||
return nil, ErrDecryption | ||
} | ||
|
||
return plaintext, nil | ||
} | ||
|
||
func (a *Cipher) initialize(nonce []byte) { | ||
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) | ||
a.s[3] ^= a.key[0] | ||
a.s[4] ^= a.key[1] | ||
} | ||
|
||
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 += 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.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 | ||
} | ||
|
||
func (a *Cipher) procText(in, out []byte, enc bool) { | ||
bcs := blockSize * int(a.mode) | ||
pB := permB + 2*(int(a.mode)-1) | ||
mask := uint64(0) | ||
if enc { | ||
mask -= 1 | ||
} | ||
|
||
for ; len(in) >= bcs; in, out = in[bcs:], out[bcs:] { | ||
for i := 0; i < bcs; 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] | ||
ss := (in[i] &^ mask8) | (out[i] & mask8) | ||
a.s[i/8] = (a.s[i/8] &^ (0xFF << off)) | uint64(ss)<<off | ||
} | ||
a.s[len(in)/8] ^= uint64(0x80) << (56 - 8*(len(in)%8)) | ||
} | ||
} | ||
|
||
func (a *Cipher) finalize(tag []byte) { | ||
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]) | ||
} | ||
|
||
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 | ||
x2 ^= roundConst[ri+i] | ||
|
||
// pS -- substitution layer | ||
// Figure 6 from Spec [DHVV18,Dae18] | ||
// https://ascon.iaik.tugraz.at/files/asconv12-nist.pdf | ||
x0 ^= x4 | ||
x4 ^= x3 | ||
x2 ^= x1 | ||
t0 := x0 & (^x4) | ||
t1 := x2 & (^x1) | ||
x0 ^= t1 | ||
t1 = x4 & (^x3) | ||
x2 ^= t1 | ||
t1 = x1 & (^x0) | ||
x4 ^= t1 | ||
t1 = x3 & (^x2) | ||
x1 ^= t1 | ||
x3 ^= t0 | ||
x1 ^= x0 | ||
x3 ^= x2 | ||
x0 ^= x4 | ||
x2 = ^x2 | ||
|
||
// pL -- linear diffusion layer | ||
x0 ^= bits.RotateLeft64(x0, -19) ^ bits.RotateLeft64(x0, -28) | ||
x1 ^= bits.RotateLeft64(x1, -61) ^ bits.RotateLeft64(x1, -39) | ||
x2 ^= bits.RotateLeft64(x2, -1) ^ bits.RotateLeft64(x2, -6) | ||
x3 ^= bits.RotateLeft64(x3, -10) ^ bits.RotateLeft64(x3, -17) | ||
x4 ^= bits.RotateLeft64(x4, -7) ^ bits.RotateLeft64(x4, -41) | ||
} | ||
a.s[0], a.s[1], a.s[2], a.s[3], a.s[4] = x0, x1, x2, x3, x4 | ||
} | ||
|
||
var ( | ||
ErrKeySize = errors.New("ascon: bad key size") | ||
ErrNonceSize = errors.New("ascon: bad nonce size") | ||
ErrDecryption = errors.New("ascon: invalid ciphertext") | ||
ErrMode = errors.New("ascon: invalid cipher mode") | ||
) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.