Add Speck-128 implementation

This commit is contained in:
2025-05-19 02:11:38 +02:00
parent 18009f8754
commit c4d242fbb3
4 changed files with 281 additions and 0 deletions

View File

@ -0,0 +1,122 @@
// impl implements the Speck algorithm. This implementation should not be used
// and instead the parent package should be used. The implementations exposes
// all the internal details for testing and analysis.
package impl
import (
"encoding/binary"
"math/bits"
"git.omicron.one/playground/cryptography/cipher"
)
const (
BlockSize128 = 128 / 8
KeySize128128 = 128 / 8
KeySize128192 = 192 / 8
KeySize128256 = 256 / 8
Rounds128128 = 32
Rounds128192 = 33
Rounds128256 = 34
)
type Speck128 struct {
Keys []uint64
}
func New128(key []byte) (*Speck128, error) {
var k [4]uint64
var m int
var rounds int
switch len(key) {
case KeySize128128:
rounds = Rounds128128
m = 1
k[0] = binary.BigEndian.Uint64(key[:8])
k[1] = binary.BigEndian.Uint64(key[8:])
case KeySize128192:
rounds = Rounds128192
m = 2
k[0] = binary.BigEndian.Uint64(key[8:])
k[1] = binary.BigEndian.Uint64(key[:8])
k[2] = binary.BigEndian.Uint64(key[16:24])
case KeySize128256:
rounds = Rounds128256
m = 3
// Note that k0, k1 and k2 are l[2], l[1] and l[0] in the spec. The
// order is reversed to make the key schedule loop below easy. The same
// is true for the 192 bit case above but with 1 less value.
k[0] = binary.BigEndian.Uint64(key[16:24])
k[1] = binary.BigEndian.Uint64(key[8:])
k[2] = binary.BigEndian.Uint64(key[:8])
k[3] = binary.BigEndian.Uint64(key[24:])
default:
return nil, cipher.ErrInvalidKeyLength
}
ctx := &Speck128{
Keys: make([]uint64, rounds),
}
ctx.Keys[0] = k[m]
for i := 0; i < rounds-1; i++ {
k[i%m], k[m] = Round128(uint64(i), k[i%m], k[m])
ctx.Keys[i+1] = k[m]
}
return ctx, nil
}
func Round128(k, x1, x2 uint64) (uint64, uint64) {
x1 = (bits.RotateLeft64(x1, 64-8) + x2) ^ k
x2 = bits.RotateLeft64(x2, 3) ^ x1
return x1, x2
}
func InverseRound128(k, x1, x2 uint64) (uint64, uint64) {
x2 = bits.RotateLeft64(x2^x1, 64-3)
x1 = bits.RotateLeft64((x1^k)-x2, 8)
return x1, x2
}
func (ctx *Speck128) Encrypt(dst, src []byte) {
if len(dst) != BlockSize128 || len(src) != BlockSize128 {
panic("Incorrect blocksize, expected 128 bits")
}
x1 := binary.BigEndian.Uint64(src[:8])
x2 := binary.BigEndian.Uint64(src[8:])
for _, k := range ctx.Keys {
x1, x2 = Round128(k, x1, x2)
}
binary.BigEndian.PutUint64(dst[:8], x1)
binary.BigEndian.PutUint64(dst[8:], x2)
}
func (ctx *Speck128) Decrypt(dst, src []byte) {
if len(dst) != BlockSize128 || len(src) != BlockSize128 {
panic("Incorrect blocksize, expected 128 bits")
}
x1 := binary.BigEndian.Uint64(src[:8])
x2 := binary.BigEndian.Uint64(src[8:])
for i := len(ctx.Keys) - 1; i >= 0; i-- {
x1, x2 = InverseRound128(ctx.Keys[i], x1, x2)
}
binary.BigEndian.PutUint64(dst[:8], x1)
binary.BigEndian.PutUint64(dst[8:], x2)
}
func (ctx *Speck128) BlockSize() int {
return BlockSize128
}
func (ctx *Speck128) Algorithm() string {
switch len(ctx.Keys) {
case Rounds128128:
return "Speck128/128"
case Rounds128192:
return "Speck128/192"
case Rounds128256:
return "Speck126/256"
}
panic("unreachable")
}