Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
Modes for cipher will be added soon

Signed-off-by: Sid Sun <[email protected]>
  • Loading branch information
Sid-Sun committed Mar 29, 2020
1 parent 9994837 commit 8e5a0e7
Show file tree
Hide file tree
Showing 5 changed files with 380 additions and 0 deletions.
257 changes: 257 additions & 0 deletions block.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
package seaturtle

import (
"encoding/binary"
)

// Encrypt one block from src into dst, using the subkeys.
func encryptBlock(subkeys []uint32, dst, src []byte) {
cryptBlock(subkeys, dst, src, false)
}

// Decrypt one block from src into dst, using the subkeys.
func decryptBlock(subkeys []uint32, dst, src []byte) {
cryptBlock(subkeys, dst, src, true)
}

func cryptBlock(subkeys []uint32, dst, src []byte, decrypt bool) {
var t uint64
left := binary.BigEndian.Uint64(src[0:8])
right := binary.BigEndian.Uint64(src[8:16])

if !decrypt {
// Input Whitening
left = left ^ concatenate32(subkeys[0], subkeys[1])
right = right ^ concatenate32(subkeys[2], subkeys[3])
for i := 0; i < 16; i++ {
t = left
left_ := feistelFunction(left, subkeys[4+(i*3)]) ^ concatenate32(subkeys[5+(i*3)], subkeys[6+(i*3)])
left = left_ ^ right
right = t
}
// Undo Last Swap
t = left
left = right
right = t
// Output Whitening
left = left ^ concatenate32(subkeys[52], subkeys[53])
right = right ^ concatenate32(subkeys[54], subkeys[55])
} else {
// Input Whitening
left = left ^ concatenate32(subkeys[52], subkeys[53])
right = right ^ concatenate32(subkeys[54], subkeys[55])

// Perform 16 feistel rounds
for i := 0; i < 16; i++ {
t = left
left_ := feistelFunction(left, subkeys[49 +(i*-3)]) ^ concatenate32(subkeys[50 +(i*-3)], subkeys[51 +(i*-3)])
left = left_ ^ right
right = t
}
// Undo Last Swap
t = left
left = right
right = t

// Output Whitening
left = left ^ concatenate32(subkeys[0], subkeys[1])
right = right ^ concatenate32(subkeys[2], subkeys[3])
}
binary.BigEndian.PutUint64(dst[0:8], left)
binary.BigEndian.PutUint64(dst[8:16], right)
}

func feistelFunction(input uint64, p uint32) uint64 {
G1 := gFunction(input, p)
// Initial PHT without schedule
for x := 0; x < 4; x += 2 {
G1[x], G1[x+1] = pht8(G1[x], G1[x+1])
G1[x+4], G1[x+5] = pht8(G1[x+4], G1[x+5])
}
// PHT With Schedule
numberOfScheduledPHTLayers := 2
var intermediate [8]uint8
for j := 0; j < numberOfScheduledPHTLayers; j++ {
for x := 0; x < 4; x += 2 {
intermediate[x], intermediate[x+1] = pht8(G1[x*2], G1[(x+1)*2])
intermediate[x+4], intermediate[x+5] = pht8(G1[1+(x*2)], G1[1+((x+1)*2)])
}
G1 = intermediate
}
return binary.BigEndian.Uint64(G1[:])
}

func gFunction(input uint64, p uint32) [8]uint8 {
var s0, s1, s2, s3, s4, s5, s6, s7 uint16
// Split 64 bit input to four 16 bit blocks
// s0, s2, s4, s6 have the original input and s1, s3, s5, s7 have derived input
s0 = uint16(input >> (3 * 16))
s2 = uint16(input >> (2 * 16))
s4 = uint16(input >> (1 * 16))
s6 = uint16(input)
// Rotate the split blocks as per data p
s0 = rotate16byN(s0, shift32ToGet4(p, 1), false)
s2 = rotate16byN(s2, shift32ToGet4(p, 2), false)
s4 = rotate16byN(s4, shift32ToGet4(p, 3), false)
s6 = rotate16byN(s6, shift32ToGet4(p, 4), false)
// Do Expansion and then DDR with data p on four 16 bit blocks
// Expansion is performed by taking 1 byte each from corresponding s0, s2, s4, s6 and concatenating
// In case of s7, the second byte for concatenation is the first byte of s0
s1 = rotate16byN(concatenate8ToGet16(shift16ToGet8(s0, 2), shift16ToGet8(s2, 1)), shift32ToGet4(p, 5), true)
s3 = rotate16byN(concatenate8ToGet16(shift16ToGet8(s2, 2), shift16ToGet8(s4, 1)), shift32ToGet4(p, 6), true)
s5 = rotate16byN(concatenate8ToGet16(shift16ToGet8(s4, 2), shift16ToGet8(s6, 1)), shift32ToGet4(p, 7), true)
s7 = rotate16byN(concatenate8ToGet16(shift16ToGet8(s6, 2), shift16ToGet8(s0, 1)), shift32ToGet4(p, 8), true)
return [8]uint8{
// There are 8 16:8 APN S Boxes
// For the corresponding array implementation, we need to split 16 bits into 8 bits
sBoxes[0][shift16ToGet8(s0, 1)][shift16ToGet8(s0, 2)],
sBoxes[1][shift16ToGet8(s1, 1)][shift16ToGet8(s1, 2)],
sBoxes[2][shift16ToGet8(s2, 1)][shift16ToGet8(s2, 2)],
sBoxes[3][shift16ToGet8(s3, 1)][shift16ToGet8(s3, 2)],
sBoxes[4][shift16ToGet8(s4, 1)][shift16ToGet8(s4, 2)],
sBoxes[5][shift16ToGet8(s5, 1)][shift16ToGet8(s5, 2)],
sBoxes[6][shift16ToGet8(s6, 1)][shift16ToGet8(s6, 2)],
sBoxes[7][shift16ToGet8(s7, 1)][shift16ToGet8(s7, 2)],
}
}

func (s seaturtleCipher) generateSubKeys(key []byte) {
numberOfWordsForInitialKey := len(key) / 4 // Number of 32 bit words needed for initial key (4,6 or 8)
nextPiWord := 0
// Put the initial key in SubKeys after XOR with a word of PI
for i := 0; i < numberOfWordsForInitialKey; i++ {
s.subkeys[i] = binary.BigEndian.Uint32(key[i*4:(i*4)+4]) ^ pi[nextPiWord]
nextPiWord++
}
switch numberOfWordsForInitialKey {
// TODO: Convert Different KeySchedules to a single, generic one
case 4:
// 128 Bit Key Schedule Rounds
numberOfRounds := 13
for i := 0; i < numberOfRounds; i++ {
G1 := gFunction(concatenate32(s.subkeys[i*4], s.subkeys[(i*4)+1]), pi[nextPiWord])
nextPiWord++
var pArray [8]uint16
// Initialize P Array
pArray[0] = concatenate8ToGet16(G1[0], G1[1])
pArray[1] = concatenate8ToGet16(G1[2], G1[3])
pArray[2] = concatenate8ToGet16(G1[4], G1[5])
pArray[3] = concatenate8ToGet16(G1[6], G1[7])
pArray[4] = shift32ToGet16(s.subkeys[(i*4)+2], 1)
pArray[5] = shift32ToGet16(s.subkeys[(i*4)+2], 2)
pArray[6] = shift32ToGet16(s.subkeys[(i*4)+3], 1)
pArray[7] = shift32ToGet16(s.subkeys[(i*4)+3], 2)
// Initial PHT without schedule
for x := 0; x < 8; x += 2 {
pArray[x], pArray[x+1] = pht16(pArray[x], pArray[x+1])
}
// PHT With Schedule
numberOfScheduledPHTLayers := numberOfWordsForInitialKey / 2
var intermediate [8]uint16
for j := 0; j < numberOfScheduledPHTLayers; j++ {
for x := 0; x < 4; x += 2 {
intermediate[x], intermediate[x+1] = pht16(pArray[x*2], pArray[(x+1)*2])
intermediate[x+4], intermediate[x+5] = pht16(pArray[1+(x*2)], pArray[1+((x+1)*2)])
}
pArray = intermediate
}
nextKeyWord := 4 + (i * 4)
for i := 0; i < 8; i += 2 {
s.subkeys[nextKeyWord] = concatenate16ToGet32(pArray[i], pArray[i+1])
nextKeyWord++
}
}
case 6:
// 192 Bit Key Schedule Rounds
numberOfRounds := 9
for i := 0; i < numberOfRounds; i++ {
G1 := gFunction(concatenate32(s.subkeys[i*6], s.subkeys[(i*6)+1]), pi[nextPiWord])
nextPiWord++
G2 := gFunction(concatenate32(s.subkeys[4+(i*6)], s.subkeys[5+(i*6)]), pi[nextPiWord])
nextPiWord++
var pArray [12]uint16
// Initialize P Array
pArray[0] = concatenate8ToGet16(G1[0], G1[1])
pArray[1] = concatenate8ToGet16(G1[2], G1[3])
pArray[2] = concatenate8ToGet16(G1[4], G1[5])
pArray[3] = concatenate8ToGet16(G1[6], G1[7])
pArray[4] = shift32ToGet16(s.subkeys[(i*6)+2], 1)
pArray[5] = shift32ToGet16(s.subkeys[(i*6)+2], 2)
pArray[6] = shift32ToGet16(s.subkeys[(i*6)+3], 1)
pArray[7] = shift32ToGet16(s.subkeys[(i*6)+3], 2)
pArray[8] = concatenate8ToGet16(G2[0], G2[1])
pArray[9] = concatenate8ToGet16(G2[2], G2[3])
pArray[10] = concatenate8ToGet16(G2[4], G2[5])
pArray[11] = concatenate8ToGet16(G2[6], G2[7])
// Initial PHT without schedule
for x := 0; x < 12; x += 2 {
pArray[x], pArray[x+1] = pht16(pArray[x], pArray[x+1])
}
// PHT With Schedule
numberOfScheduledPHTLayers := numberOfWordsForInitialKey / 2
var intermediate [12]uint16
for j := 0; j < numberOfScheduledPHTLayers; j++ {
for x := 0; x < 6; x += 2 {
intermediate[x], intermediate[x+1] = pht16(pArray[x*2], pArray[(x+1)*2])
intermediate[x+6], intermediate[x+7] = pht16(pArray[1+(x*2)], pArray[1+((x+1)*2)])
}
pArray = intermediate
}
nextKeyWord := 6 + (i * 6)
for i := 0; i < 12; i += 2 {
if !(nextKeyWord > 56) { // 192 Bit KeySchedule generates more subkeys than necessary, ensure they are not added
s.subkeys[nextKeyWord] = concatenate16ToGet32(pArray[i], pArray[i+1])
nextKeyWord++
} else {
break
}
}
}
case 8:
// 256 Bit Key Schedule Rounds
numberOfRounds := 6
for i := 0; i < numberOfRounds; i++ {
G1 := gFunction(concatenate32(s.subkeys[i*8], s.subkeys[(i*8)+1]), pi[nextPiWord])
nextPiWord++
G2 := gFunction(concatenate32(s.subkeys[4+(i*8)], s.subkeys[5+(i*8)]), pi[nextPiWord])
nextPiWord++
var pArray [16]uint16
// Initialize P Array
pArray[0] = concatenate8ToGet16(G1[0], G1[1])
pArray[1] = concatenate8ToGet16(G1[2], G1[3])
pArray[2] = concatenate8ToGet16(G1[4], G1[5])
pArray[3] = concatenate8ToGet16(G1[6], G1[7])
pArray[4] = shift32ToGet16(s.subkeys[(i*8)+2], 1)
pArray[5] = shift32ToGet16(s.subkeys[(i*8)+2], 2)
pArray[6] = shift32ToGet16(s.subkeys[(i*8)+3], 1)
pArray[7] = shift32ToGet16(s.subkeys[(i*8)+3], 2)
pArray[8] = concatenate8ToGet16(G2[0], G2[1])
pArray[9] = concatenate8ToGet16(G2[2], G2[3])
pArray[10] = concatenate8ToGet16(G2[4], G2[5])
pArray[11] = concatenate8ToGet16(G2[6], G2[7])
pArray[12] = shift32ToGet16(s.subkeys[(i*8)+6], 1)
pArray[13] = shift32ToGet16(s.subkeys[(i*8)+6], 2)
pArray[14] = shift32ToGet16(s.subkeys[(i*8)+7], 1)
pArray[15] = shift32ToGet16(s.subkeys[(i*8)+7], 2)
// Initial PHT without schedule
for x := 0; x < 16; x += 2 {
pArray[x], pArray[x+1] = pht16(pArray[x], pArray[x+1])
}
// PHT With Schedule
numberOfScheduledPHTLayers := numberOfWordsForInitialKey / 2
var intermediate [16]uint16
for j := 0; j < numberOfScheduledPHTLayers; j++ {
for x := 0; x < 8; x += 2 {
intermediate[x], intermediate[x+1] = pht16(pArray[x*2], pArray[(x+1)*2])
intermediate[x+8], intermediate[x+9] = pht16(pArray[1+(x*2)], pArray[1+((x+1)*2)])
}
pArray = intermediate
}
nextKeyWord := 8 + (i * 8)
for i := 0; i < 16; i += 2 {
s.subkeys[nextKeyWord] = concatenate16ToGet32(pArray[i], pArray[i+1])
nextKeyWord++
}
}
}
}
61 changes: 61 additions & 0 deletions cipher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package seaturtle

import (
"crypto/cipher"
"strconv"
)

type seaturtleCipher struct {
subkeys [56]uint32
}

const BlockSize = 16

type KeySizeError int

func (k KeySizeError) Error() string {
return "seaturtle: invalid key size " + strconv.Itoa(int(k))
}

func NewCipher(key []byte) (cipher.Block, error) {

switch len(key) {
case 16, 24, 32:
break
default:
return nil, KeySizeError(len(key))
}

c := new(seaturtleCipher)
c.generateSubKeys(key)

return c, nil
}

func (s seaturtleCipher) BlockSize() int {
return BlockSize
}

func (s seaturtleCipher) Encrypt(dst, src []byte) {
if len(src) < BlockSize {
panic("seaturtle: input not full block")
}

if len(dst) < BlockSize {
panic("seaturtle: output not full block")
}

encryptBlock(s.subkeys[:], dst, src)
}

func (s seaturtleCipher) Decrypt(dst, src []byte) {
if len(src) < BlockSize {
panic("seaturtle: input not full block")
}

if len(dst) < BlockSize {
panic("seaturtle: output not full block")
}

decryptBlock(s.subkeys[:], dst, src)
}
3 changes: 3 additions & 0 deletions const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package seaturtle

var pi = [26]uint32{0x243F6A88, 0x85A308D3, 0x13198A2E, 0x03707344, 0xA4093822, 0x299F31D0, 0x082EFA98, 0xEC4E6C89, 0x452821E6, 0x38D01377, 0xBE5466CF, 0x34E90C6C, 0xC0AC29B7, 0xC97C50DD, 0x3F84D5B5, 0xB5470917, 0x9216D5D9, 0x8979FB1B, 0xD1310BA6, 0x98DFB5AC, 0x2FFD72DB, 0xD01ADFB7, 0xB8E1AFED, 0x6A267E96, 0xBA7C9045, 0xF12C7F99}
12 changes: 12 additions & 0 deletions const_sbox.go

Large diffs are not rendered by default.

47 changes: 47 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package seaturtle

func pht8(a byte, b byte) (byte, byte) {
a = (a + b) % byte(255)
b = (a + b) % byte(255)
return a, b
}

func pht16(a uint16, b uint16) (uint16, uint16) {
a = (a + b) % uint16(65535)
b = (a + b) % uint16(65535)
return a, b
}

func concatenate8ToGet16(a uint8, b uint8) uint16 {
return (uint16(a) << 8) | uint16(b)
}

func concatenate16ToGet32(a, b uint16) uint32 {
return (uint32(a) << 16) | uint32(b)
}

func concatenate32(a uint32, b uint32) uint64 {
return (uint64(a) << 32) | uint64(b)
}

func rotate16byN(x uint16, N uint8, ShiftRight bool) uint16 {
if ShiftRight {
return x>>N | x<<(16-N)
} else {
return x<<N | x>>(16-N)
}
}

func shift16ToGet8(x uint16, ind uint8) uint8 {
// Indexing must start at 1
return uint8((x << ((ind - 1) * 8)) >> (8))
}

func shift32ToGet4(x uint32, ind uint8) uint8 {
// Indexing must start at 1
return uint8((x << ((ind - 1) * 4)) >> (7 * 4))
}
func shift32ToGet16(x uint32, ind uint32) uint16 {
// Indexing must start at 1
return uint16(x<<((ind-1)*16)) >> (16)
}

0 comments on commit 8e5a0e7

Please sign in to comment.