From b5f181cd7003dcfd6402aa66e57c66a4707c211a Mon Sep 17 00:00:00 2001 From: deatil <2217957370@qq.com> Date: Wed, 14 Aug 2024 12:16:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cipher/cascade/cascade.go | 99 ++++++++++++++++ cipher/cascade/cascade_test.go | 163 ++++++++++++++++++++++++++ cipher/lion/lion.go | 142 ++++++++++++++++++++++ cipher/lion/lion_test.go | 100 ++++++++++++++++ cipher/shacal2/sbox.go | 12 ++ cipher/shacal2/shacal2.go | 137 ++++++++++++++++++++++ cipher/shacal2/shacal2_test.go | 163 ++++++++++++++++++++++++++ cipher/shacal2/utils.go | 90 ++++++++++++++ tool/math/gcd/extended.go | 10 ++ tool/math/gcd/extended_test.go | 24 ++++ tool/math/gcd/extendedgcd.go | 12 ++ tool/math/gcd/extendedgcd_test.go | 56 +++++++++ tool/math/gcd/extendedgcditerative.go | 13 ++ tool/math/gcd/gcd.go | 9 ++ tool/math/gcd/gcd_test.go | 49 ++++++++ tool/math/gcd/gcditerative.go | 9 ++ tool/math/lcm/lcm.go | 12 ++ tool/math/lcm/lcm_test.go | 44 +++++++ 18 files changed, 1144 insertions(+) create mode 100644 cipher/cascade/cascade.go create mode 100644 cipher/cascade/cascade_test.go create mode 100644 cipher/lion/lion.go create mode 100644 cipher/lion/lion_test.go create mode 100644 cipher/shacal2/sbox.go create mode 100644 cipher/shacal2/shacal2.go create mode 100644 cipher/shacal2/shacal2_test.go create mode 100644 cipher/shacal2/utils.go create mode 100644 tool/math/gcd/extended.go create mode 100644 tool/math/gcd/extended_test.go create mode 100644 tool/math/gcd/extendedgcd.go create mode 100644 tool/math/gcd/extendedgcd_test.go create mode 100644 tool/math/gcd/extendedgcditerative.go create mode 100644 tool/math/gcd/gcd.go create mode 100644 tool/math/gcd/gcd_test.go create mode 100644 tool/math/gcd/gcditerative.go create mode 100644 tool/math/lcm/lcm.go create mode 100644 tool/math/lcm/lcm_test.go diff --git a/cipher/cascade/cascade.go b/cipher/cascade/cascade.go new file mode 100644 index 00000000..5bcd7400 --- /dev/null +++ b/cipher/cascade/cascade.go @@ -0,0 +1,99 @@ +package cascade + +import ( + "errors" + "crypto/cipher" + + "github.com/deatil/go-cryptobin/tool/alias" + "github.com/deatil/go-cryptobin/tool/math/lcm" +) + +type cascadeCipher struct { + cipher1 cipher.Block + cipher2 cipher.Block + bs int +} + +// New creates and returns a new cipher.Block. +func NewCipher(cip1, cip2 cipher.Block) (cipher.Block, error) { + c := new(cascadeCipher) + c.cipher1 = cip1 + c.cipher2 = cip2 + c.bs = int(lcm.Lcm(int64(cip1.BlockSize()), int64(cip2.BlockSize()))) + + if !(c.bs % cip1.BlockSize() == 0 && + c.bs % cip2.BlockSize() == 0) { + return nil, errors.New("Combined block size is a multiple of each ciphers block") + } + + return c, nil +} + +func (this *cascadeCipher) BlockSize() int { + return this.bs +} + +func (this *cascadeCipher) Encrypt(dst, src []byte) { + bs := this.bs + + if len(src) < bs { + panic("cryptobin/cascade: input not full block") + } + + if len(dst) < bs { + panic("cryptobin/cascade: output not full block") + } + + if alias.InexactOverlap(dst[:bs], src[:bs]) { + panic("cryptobin/cascade: invalid buffer overlap") + } + + this.encrypt(dst, src) +} + +func (this *cascadeCipher) Decrypt(dst, src []byte) { + bs := this.bs + + if len(src) < bs { + panic("cryptobin/cascade: input not full block") + } + + if len(dst) < bs { + panic("cryptobin/cascade: output not full block") + } + + if alias.InexactOverlap(dst[:bs], src[:bs]) { + panic("cryptobin/cascade: invalid buffer overlap") + } + + this.decrypt(dst, src) +} + +func (this *cascadeCipher) encrypt(dst, src []byte) { + bs1 := this.cipher1.BlockSize() + bs2 := this.cipher2.BlockSize() + + for i := 0; i < len(src); i += bs1 { + this.cipher1.Encrypt(dst[i:i+bs1], src[i:i+bs1]) + } + + for i := 0; i < len(dst); i += bs2 { + this.cipher2.Encrypt(dst[i:i+bs2], dst[i:i+bs2]) + } +} + +func (this *cascadeCipher) decrypt(dst, src []byte) { + bs1 := this.cipher1.BlockSize() + bs2 := this.cipher2.BlockSize() + + src = src[:this.bs] + dst = dst[:this.bs] + + for i := 0; i < len(dst); i += bs2 { + this.cipher2.Decrypt(dst[i:i+bs2], src[i:i+bs2]) + } + + for i := 0; i < len(src); i += bs1 { + this.cipher1.Decrypt(dst[i:i+bs1], dst[i:i+bs1]) + } +} diff --git a/cipher/cascade/cascade_test.go b/cipher/cascade/cascade_test.go new file mode 100644 index 00000000..9b70fa3f --- /dev/null +++ b/cipher/cascade/cascade_test.go @@ -0,0 +1,163 @@ +package cascade + +import ( + "bytes" + "testing" + "math/rand" + "crypto/aes" + "crypto/des" + "crypto/cipher" + "encoding/hex" + + "golang.org/x/crypto/cast5" + "golang.org/x/crypto/twofish" + "github.com/deatil/go-cryptobin/cipher/serpent" + cryptobin_cipher "github.com/deatil/go-cryptobin/cipher" +) + +func fromHex(s string) []byte { + h, _ := hex.DecodeString(s) + return h +} + +func Test_Cipher(t *testing.T) { + random := rand.New(rand.NewSource(99)) + max := 100 + + var encrypted [16]byte + var decrypted [16]byte + + for i := 0; i < max; i++ { + key := make([]byte, 16) + random.Read(key) + key2 := make([]byte, 8) + random.Read(key2) + value := make([]byte, 16) + random.Read(value) + + c1, err := aes.NewCipher(key) + if err != nil { + t.Fatal(err) + } + c2, err := des.NewCipher(key2) + if err != nil { + t.Fatal(err) + } + + cipher1, err := NewCipher(c1, c2) + if err != nil { + t.Fatal(err.Error()) + } + + cipher1.Encrypt(encrypted[:], value) + + if bytes.Equal(encrypted[:], value[:]) { + t.Errorf("fail: encrypted equal plaintext \n") + } + + cipher2, err := NewCipher(c1, c2) + if err != nil { + t.Fatal(err.Error()) + } + + cipher2.Decrypt(decrypted[:], encrypted[:]) + + if !bytes.Equal(decrypted[:], value[:]) { + t.Errorf("encryption/decryption failed: % 02x != % 02x\n", decrypted, value) + } + } +} + +var newTwofishCipher = func(key []byte) (cipher.Block, error) { + return twofish.NewCipher(key) +} + +var newCast5Cipher = func(key []byte) (cipher.Block, error) { + return cast5.NewCipher(key) +} + +var cipTests = []struct { + cip1 func([]byte) (cipher.Block, error) + cip2 func([]byte) (cipher.Block, error) + key []byte + plain []byte + cipher []byte +}{ + // [Cascade(Serpent,Twofish)] + { + serpent.NewCipher, + newTwofishCipher, + fromHex("B50638F695AFA16F9378D43374CA8568600135ECD1E513838722366346BC4B2101422291558FAA30A3196CBEB42E67F4C075882482897F72A8A30AE9B3AD426D"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("E78516D21D23DA501939C24C48BCC79DE78516D21D23DA501939C24C48BCC79D"), + }, + { + serpent.NewCipher, + newTwofishCipher, + fromHex("9E8F6BC09768AED8F533FA4FC35FF6FEB8020FFBC8350DDFD20ACA7ECF1889CFBFCD78E261B9A3CD825401AFA7ADCDFA88DBA8230FB92D4B942C25EE92F27A02"), + fromHex("47CB8147C5290D6F94FBF3351777087FA731610A3F66E3CCFA6D9B18F980E687"), + fromHex("F234E056923B3DB26AABC8F604F0CE2C1A7F4C35B0B74958014D791668FF6BF4"), + }, + { + serpent.NewCipher, + newTwofishCipher, + fromHex("1EF34E47005028F2D95120052855C6001225200A333CA4D7D5A356B5554EE2AE7EBC9BA57BADA0DAFC84C2187C51CB3CCB5EEE40F27C00537FFFCA2851DD8BD8"), + fromHex("B9A28D32734EF678BACD5539FF9FF951AF81F44AFE223256E5D8898FB862A767B90BD2D95E17E4411D02D49481CCE4191EE2C7AE8EBDF6312BDC66317AD42140"), + fromHex("065E390C4FD10E9929F30D89A67E0D4CFA3AF90BEF46B2B435B53CBE0B7DD1B612D4C5E2D03028B488000C06517434FC70F7B62C273CA5DEBD9CA7034D853087"), + }, + + // [Cascade(Serpent,AES-256)] + { + serpent.NewCipher, + aes.NewCipher, + fromHex("EE426051D1ADCE09AC02E2023331F273BB1B2C4C5905DEDA3E1032CCD0DB56115B011F05688F781E3F790364968E06DC6E7BD5FA38DB068CBD34A85B6B3A9458"), + fromHex("06CEB2B4FD2F0A27B3C90D77D2E9BBD3665A8DCAC9187B1EE9F6A60D39042A9D3719883B3E87845B9D4A8BE258379959775969CBF5768A359797B2FA19FC2FCC"), + fromHex("05FFBF6E8097FC746FFAD8C3306E6DB668148796180F26CA5DE06AE76DE16D078A0E72B259982423ED96FF95719DEB160CEFE7697752B0CFA984A18DDCEF2EC0"), + }, + { + serpent.NewCipher, + aes.NewCipher, + fromHex("CDCD23F5518DB5DAE8C69B56EB352D4F3C4A64A5FFC8E5BC2511B8310993C48EFA30A0F9E2B98A0FB1FE64173E6A8038047AEBAE22E17392FE32CF1D0DE3BB76"), + fromHex("FBAF0DE6C09D10EB31F21A7C784BF453F82F51EFFA8B363EE6B33DF15204F43445170DED1E39AB922548ED82AAADED6BF470A5226B69D025FE3D532AADDA069C464D2C8A65E1A18698BD521AFB3053229C1539626392031F8C36229FF3178A7F5C716E30DBEFDDD4AC2113071977B795A8B29DA7F467471A996FB63136387C28"), + fromHex("7ED1F730EED52DFB63E073A40EAE404E443ACEB9A3B55132E740ACE1EEDF99D0F22B3F2326E2E124594E75ED1915C8D155F24269254B22B6E8C53E9F64E70552D5E3004782C6C47341EBF8716B59DAB49B512B6DF7F9D7FB914FFA56F7F89B561B6A5DFE9334B7561144B25FE0F57BEBB4058EC7D9EEA57AB62825A86312BBC3"), + }, + + // [Cascade(Serpent,CAST-128)] + { + serpent.NewCipher, + newCast5Cipher, + fromHex("EFA9CC5F3E245AB463CC60A5015CB0F663676760832CEE6C633A518112E518D45DD4B627E9507CDB03A1ADD870E28362"), + fromHex("27EDE4B2A3784A33898FA330167317BF7354072672D49DD03D13D3F0856CF3D9C17C1237565E7320BDD23C03BDE195A4FE58623A983DB9C308D5A976D92CD6A2"), + fromHex("2D7096A03BAB4DBDABEDB9F069FE68C3E12ED65ACCE43ECF7F6D810B5EEC36A522B605715BE12003E324436652BEA06BD289DBE886A5DE9E51CFF6C065A21F2B"), + }, +} + +func Test_Check(t *testing.T) { + for _, tt := range cipTests { + c1, err := tt.cip1(tt.key[:32]) + if err != nil { + t.Fatal(err) + } + c2, err := tt.cip2(tt.key[32:]) + if err != nil { + t.Fatal(err) + } + + c, _ := NewCipher(c1, c2) + + b := make([]byte, len(tt.plain)) + cryptobin_cipher. + NewECBEncrypter(c). + CryptBlocks(b[:], tt.plain) + if !bytes.Equal(b[:], tt.cipher) { + t.Errorf("encrypt failed: got %x, want %x", b, tt.cipher) + } + + cryptobin_cipher. + NewECBDecrypter(c). + CryptBlocks(b[:], tt.cipher) + if !bytes.Equal(b[:], tt.plain) { + t.Errorf("decrypt failed: got %x, want %x", b, tt.plain) + } + } +} diff --git a/cipher/lion/lion.go b/cipher/lion/lion.go new file mode 100644 index 00000000..1d4a457b --- /dev/null +++ b/cipher/lion/lion.go @@ -0,0 +1,142 @@ +package lion + +import ( + "fmt" + "hash" + "crypto/cipher" + "crypto/subtle" + + "github.com/deatil/go-cryptobin/tool/alias" +) + +type Streamer = func([]byte) (cipher.Stream, error) + +type lionCipher struct { + key1 []byte + key2 []byte + + hash hash.Hash + cipher Streamer + bs int +} + +// NewCipher creates and returns a new cipher.Block. +func NewCipher(hash hash.Hash, cipher Streamer, bs int, key []byte) (cipher.Block, error) { + c := new(lionCipher) + + c.hash = hash + c.cipher = cipher + c.bs = mathMax(2 * hash.Size() + 1, bs) + + if 2 * c.leftSize() + 1 > c.bs { + return nil, fmt.Errorf("Block size %d is too small", c.bs) + } + + c.expandKey(key) + + return c, nil +} + +func (this *lionCipher) BlockSize() int { + return this.bs +} + +func (this *lionCipher) Encrypt(dst, src []byte) { + bs := this.bs + + if len(src) < bs { + panic(fmt.Sprintf("cryptobin/lion: invalid block size %d (src)", len(src))) + } + + if len(dst) < bs { + panic(fmt.Sprintf("cryptobin/lion: invalid block size %d (dst)", len(dst))) + } + + if alias.InexactOverlap(dst[:bs], src[:bs]) { + panic("cryptobin/lion: invalid buffer overlap") + } + + this.encrypt(dst, src) +} + +func (this *lionCipher) Decrypt(dst, src []byte) { + bs := this.bs + + if len(src) < bs { + panic(fmt.Sprintf("cryptobin/lion: invalid block size %d (src)", len(src))) + } + + if len(dst) < bs { + panic(fmt.Sprintf("cryptobin/lion: invalid block size %d (dst)", len(dst))) + } + + if alias.InexactOverlap(dst[:bs], src[:bs]) { + panic("cryptobin/lion: invalid buffer overlap") + } + + this.decrypt(dst, src) +} + +func (this *lionCipher) encrypt(dst, src []byte) { + leftSize := this.leftSize() + + buffer := make([]byte, leftSize) + + subtle.XORBytes(buffer, src[:leftSize], this.key1) + cip, _ := this.cipher(buffer) + cip.XORKeyStream(dst[leftSize:], src[leftSize:]) + + this.hash.Reset() + this.hash.Write(dst[leftSize:]) + buffer = this.hash.Sum(nil) + subtle.XORBytes(dst[:leftSize], src[:leftSize], buffer) + + subtle.XORBytes(buffer, dst[:leftSize], this.key2) + cip, _ = this.cipher(buffer) + cip.XORKeyStream(dst[leftSize:], dst[leftSize:]) +} + +func (this *lionCipher) decrypt(dst, src []byte) { + leftSize := this.leftSize() + + buffer := make([]byte, leftSize) + + subtle.XORBytes(buffer, src[:leftSize], this.key2) + cip, _ := this.cipher(buffer) + cip.XORKeyStream(dst[leftSize:], src[leftSize:]) + + this.hash.Reset() + this.hash.Write(dst[leftSize:]) + buffer = this.hash.Sum(nil) + subtle.XORBytes(dst[:leftSize], src[:leftSize], buffer) + + subtle.XORBytes(buffer, dst[:leftSize], this.key1) + cip, _ = this.cipher(buffer) + cip.XORKeyStream(dst[leftSize:], dst[leftSize:]) +} + +func (this *lionCipher) expandKey(key []byte) { + half := len(key) / 2 + + this.key1 = make([]byte, this.leftSize()) + this.key2 = make([]byte, this.leftSize()) + + copy(this.key1, key[:half]) + copy(this.key2, key[half:]) +} + +func (this *lionCipher) leftSize() int { + return this.hash.Size() +} + +func (this *lionCipher) rightSize() int { + return this.bs - this.leftSize() +} + +func mathMax(a, b int) int { + if a > b { + return a + } + + return b +} diff --git a/cipher/lion/lion_test.go b/cipher/lion/lion_test.go new file mode 100644 index 00000000..338e75f2 --- /dev/null +++ b/cipher/lion/lion_test.go @@ -0,0 +1,100 @@ +package lion + +import ( + "hash" + "bytes" + "testing" + "math/rand" + "crypto/md5" + "crypto/rc4" + "crypto/sha1" + "crypto/cipher" + "encoding/hex" +) + +func fromHex(s string) []byte { + h, _ := hex.DecodeString(s) + return h +} + +var newRc4Cipher = func(key []byte) (cipher.Stream, error) { + return rc4.NewCipher(key) +} + +func Test_Cipher(t *testing.T) { + random := rand.New(rand.NewSource(99)) + max := 100 + + var encrypted [36]byte + var decrypted [36]byte + + for i := 0; i < max; i++ { + key := make([]byte, 16) + random.Read(key) + value := make([]byte, 36) + random.Read(value) + + cipher1, err := NewCipher(md5.New(), newRc4Cipher, 36, key) + if err != nil { + t.Fatal(err.Error()) + } + + cipher1.Encrypt(encrypted[:], value) + + if bytes.Equal(encrypted[:], value[:]) { + t.Errorf("fail: encrypted equal plaintext \n") + } + + cipher2, err := NewCipher(md5.New(), newRc4Cipher, 36, key) + if err != nil { + t.Fatal(err.Error()) + } + + cipher2.Decrypt(decrypted[:], encrypted[:]) + + if !bytes.Equal(decrypted[:], value[:]) { + t.Errorf("encryption/decryption failed: % 02x != % 02x\n", decrypted, value) + } + } +} + +var cipTests = []struct { + hash hash.Hash + cip Streamer + bs int + key []byte + plain []byte + cipher []byte +}{ + // [Lion(SHA-1,RC4,64)] + { + sha1.New(), + newRc4Cipher, + 64, + fromHex("00112233445566778899AABBCCDDEEFF00112233445566778899AABBCCDDEEFF"), + fromHex("1112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F3031323334353637382015B3DB2DC49529C2D26B1F1E86C65EC7B946AB2D2E2F30"), + fromHex("BCE3BE866EF63AF5AD4CBA8C3CAA2AA9CF9BB3CC2A3D77FF7C05D0EC7E684AD6134ABFD7DF6842B7292071064C9F4DFE4B9D34EAE89201136B7CE70ED4A190DB"), + }, + +} + +func Test_Check(t *testing.T) { + for _, tt := range cipTests { + c, err := NewCipher(tt.hash, tt.cip, tt.bs, tt.key) + if err != nil { + t.Fatal(err) + } + + b := make([]byte, len(tt.plain)) + c.Encrypt(b[:], tt.plain) + if !bytes.Equal(b[:], tt.cipher) { + t.Errorf("encrypt failed: got %x, want %x", b, tt.cipher) + } + + c.Decrypt(b[:], tt.cipher) + if !bytes.Equal(b[:], tt.plain) { + t.Errorf("decrypt failed: got %x, want %x", b, tt.plain) + } + } +} + diff --git a/cipher/shacal2/sbox.go b/cipher/shacal2/sbox.go new file mode 100644 index 00000000..88349aaa --- /dev/null +++ b/cipher/shacal2/sbox.go @@ -0,0 +1,12 @@ +package shacal2 + +var rc = [64]uint32{ + 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, + 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, + 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, + 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, + 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, + 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, + 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, + 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2, +} diff --git a/cipher/shacal2/shacal2.go b/cipher/shacal2/shacal2.go new file mode 100644 index 00000000..8f67ac11 --- /dev/null +++ b/cipher/shacal2/shacal2.go @@ -0,0 +1,137 @@ +package shacal2 + +import ( + "fmt" + "crypto/cipher" + + "github.com/deatil/go-cryptobin/tool/alias" +) + +const BlockSize = 32 + +type KeySizeError int + +func (k KeySizeError) Error() string { + return fmt.Sprintf("cryptobin/shacal2: invalid key size %d", int(k)) +} + +type shacal2Cipher struct { + roundKey []uint32 +} + +// NewCipher creates and returns a new cipher.Block. +func NewCipher(key []byte) (cipher.Block, error) { + k := len(key) + if k < 4 || k > 256 { + return nil, KeySizeError(k) + } + + c := new(shacal2Cipher) + c.expandKey(key) + + return c, nil +} + +func (this *shacal2Cipher) BlockSize() int { + return BlockSize +} + +func (this *shacal2Cipher) Encrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("cryptobin/shacal2: input not full block") + } + + if len(dst) < BlockSize { + panic("cryptobin/shacal2: output not full block") + } + + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("cryptobin/shacal2: invalid buffer overlap") + } + + this.encrypt(dst, src) +} + +func (this *shacal2Cipher) Decrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("cryptobin/shacal2: input not full block") + } + + if len(dst) < BlockSize { + panic("cryptobin/shacal2: output not full block") + } + + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("cryptobin/shacal2: invalid buffer overlap") + } + + this.decrypt(dst, src) +} + +func (this *shacal2Cipher) encrypt(out []byte, in []byte) { + A := getu32(in[0:]) + B := getu32(in[4:]) + C := getu32(in[8:]) + D := getu32(in[12:]) + E := getu32(in[16:]) + F := getu32(in[20:]) + G := getu32(in[24:]) + H := getu32(in[28:]) + + for r := 0; r < 64; r += 8 { + fwd(A, B, C, &D, E, F, G, &H, this.roundKey[r + 0]) + fwd(H, A, B, &C, D, E, F, &G, this.roundKey[r + 1]) + fwd(G, H, A, &B, C, D, E, &F, this.roundKey[r + 2]) + fwd(F, G, H, &A, B, C, D, &E, this.roundKey[r + 3]) + fwd(E, F, G, &H, A, B, C, &D, this.roundKey[r + 4]) + fwd(D, E, F, &G, H, A, B, &C, this.roundKey[r + 5]) + fwd(C, D, E, &F, G, H, A, &B, this.roundKey[r + 6]) + fwd(B, C, D, &E, F, G, H, &A, this.roundKey[r + 7]) + } + + res := uint32sToBytes([]uint32{A, B, C, D, E, F, G, H}) + copy(out, res) +} + +func (this *shacal2Cipher) decrypt(out []byte, in []byte) { + A := getu32(in[0:]) + B := getu32(in[4:]) + C := getu32(in[8:]) + D := getu32(in[12:]) + E := getu32(in[16:]) + F := getu32(in[20:]) + G := getu32(in[24:]) + H := getu32(in[28:]) + + for r := 0; r < 64; r += 8 { + rev(B, C, D, &E, F, G, H, &A, this.roundKey[63 - r]) + rev(C, D, E, &F, G, H, A, &B, this.roundKey[62 - r]) + rev(D, E, F, &G, H, A, B, &C, this.roundKey[61 - r]) + rev(E, F, G, &H, A, B, C, &D, this.roundKey[60 - r]) + rev(F, G, H, &A, B, C, D, &E, this.roundKey[59 - r]) + rev(G, H, A, &B, C, D, E, &F, this.roundKey[58 - r]) + rev(H, A, B, &C, D, E, F, &G, this.roundKey[57 - r]) + rev(A, B, C, &D, E, F, G, &H, this.roundKey[56 - r]) + } + + res := uint32sToBytes([]uint32{A, B, C, D, E, F, G, H}) + copy(out, res) +} + +func (this *shacal2Cipher) expandKey(key []byte) { + this.roundKey = make([]uint32, 64) + + keyUint32s := bytesToUint32s(key) + copy(this.roundKey, keyUint32s) + + for i := 16; i < 64; i++ { + sigma0_15 := sigma(this.roundKey[i - 15], 7, 18, 3) + sigma1_2 := sigma(this.roundKey[i - 2], 17, 19, 10) + + this.roundKey[i] = this.roundKey[i - 16] + sigma0_15 + this.roundKey[i - 7] + sigma1_2 + } + + for i := 0; i < 64; i++ { + this.roundKey[i] += rc[i] + } +} diff --git a/cipher/shacal2/shacal2_test.go b/cipher/shacal2/shacal2_test.go new file mode 100644 index 00000000..af27a127 --- /dev/null +++ b/cipher/shacal2/shacal2_test.go @@ -0,0 +1,163 @@ +package shacal2 + +import ( + "bytes" + "testing" + "math/rand" + "encoding/hex" + "github.com/deatil/go-cryptobin/cipher" +) + +func fromHex(s string) []byte { + h, _ := hex.DecodeString(s) + return h +} + +func Test_Cipher(t *testing.T) { + random := rand.New(rand.NewSource(99)) + max := 10 + + var encrypted [32]byte + var decrypted [32]byte + + for i := 0; i < max; i++ { + key := make([]byte, 64) + random.Read(key) + value := make([]byte, 32) + random.Read(value) + + cipher1, err := NewCipher(key) + if err != nil { + t.Fatal(err.Error()) + } + + cipher1.Encrypt(encrypted[:], value) + + if bytes.Equal(encrypted[:], value[:]) { + t.Errorf("fail: encrypted equal plaintext \n") + } + + cipher2, err := NewCipher(key) + if err != nil { + t.Fatal(err.Error()) + } + + cipher2.Decrypt(decrypted[:], encrypted[:]) + + if !bytes.Equal(decrypted[:], value[:]) { + t.Errorf("encryption/decryption failed: % 02x != % 02x\n", decrypted, value) + } + } +} + +var cipTests = []struct { + key []byte + plain []byte + cipher []byte +}{ + // From Bouncy Castle + { + fromHex("000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F"), + fromHex("98BCC10405AB0BFC686BECECAAD01AC19B452511BCEB9CB094F905C51CA45430"), + fromHex("00112233445566778899AABBCCDDEEFF102132435465768798A9BACBDCEDFE0F"), + }, + + // Tests for short key handling + { + fromHex("80000000000000000000000000000000"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("361AB6322FA9E7A7BB23818D839E01BDDAFDF47305426EDD297AEDB9F6202BAE"), + }, + { + fromHex("8000000000000000000000000000000000000000"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("361AB6322FA9E7A7BB23818D839E01BDDAFDF47305426EDD297AEDB9F6202BAE"), + }, + + // From NESSIE submission package via Crypto++ + { + fromHex("80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("361AB6322FA9E7A7BB23818D839E01BDDAFDF47305426EDD297AEDB9F6202BAE"), + }, + { + fromHex("40000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("F3BAF53E5301E08813F8BE6F651BB19E9722151FF15063BA42A6FEF7CF3BF3D7"), + }, + { + fromHex("20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("E485005217441B60EE5B48EE8AF924B268B6B952D7F593E6102AC83D7DA72838"), + }, + { + fromHex("00100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("77CEC8EA64BB7FAE966D030FE4CF318C318DBEBAEB896F31FAA3C9CBA0AE125D"), + }, + { + fromHex("00000000000000000000080000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + fromHex("B38604950FA73165F940D4DB527D09CD0B233276CD3808B5CADCCB9FA859AEEB"), + }, + { + fromHex("2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C"), + fromHex("2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C2C"), + fromHex("DBF9B56BBF2E50DF321CA687F8BE0E6222E7DF52B4A142174058CC119D9EC0DA"), + }, + { + fromHex("2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D"), + fromHex("2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D2D"), + fromHex("8B2757374F778FE0B30D11AD7116CE37E2AB858A4E1C50D1115B6E328F3635F5"), + }, + { + fromHex("50505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050505050"), + fromHex("5050505050505050505050505050505050505050505050505050505050505050"), + fromHex("BE28CB05EEEEDA8FD8971E9970ECBCA25856F66E95AC8B987C69F04BE3276CD7"), + }, + { + fromHex("EFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEF"), + fromHex("EFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEFEF"), + fromHex("5F45E7F72A64C66269F83714A88A0701561C3E7AF33BB48887D4439F5DE4A82D"), + }, + { + fromHex("F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8"), + fromHex("F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8F8"), + fromHex("B67638D30578AB2319FE275D0B833B50D7ABF01E8760F566D0D441D8EAFDF8AA"), + }, + { + fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + fromHex("0598127BAF11706F77402000D730C54A0B84C868A98C6CA4D7F3C0FA06A78B7A"), + }, + + // Randomly generated by Crypto++ + { + fromHex("67C6697351FF4AEC29CDBAABF2FBE3467CC254F81BE8E78D765A2E63339FC99A"), + fromHex("66320DB73158A35A255D051758E95ED4ABB2CDC69BB454110E827441213DDC8770E93EA141E1FC673E017E97EADC6B968F385C2AECB03BFB32AF3C54EC18DB5C021AFE43FBFAAA3AFB29D1E6053C7C9475D8BE6189F95CBBA8990F95B1EBF1B305EFF700E9A13AE5CA0BCBD0484764BD1F231EA81C7B64C514735AC55E4B79633B706424119E09DCAAD4ACF21B10AF3B33CDE3504847155CBB6F2219BA9B7DF50BE11A1C7F23F829F8A41B13B5CA4EE8983238E0794D3D34BC5F4E77FACB6C05AC86212BAA1A55A2BE70B5733B045CD33694B3AFE2F0E49E4F321549FD824EA90870D4B28A2954489A0ABCD50E18A844AC5BF38E4CD72D9B0942E506C433AFCDA3847F2DADD47647DE321CEC4AC430F62023856CFBB20704F4EC0BB920BA86C33E05F1ECD96733B79950A3E314D3D934F75EA0F210A8F6059401BEB4BC4478FA4969E623D01ADA696A7E4C7E5125B34884533A94FB319990325744EE9BBCE9E525CF08F5E9E25E5360AAD2B2D085FA54D835E8D466826498D9A8877565705A8A3F62802944DE7CA5894E5759D351ADAC869580EC17E485F18C0C66F17CC07CBB22FCE466DA610B63AF62BC83B4692F3AFFAF271693AC071FB86D11342D8DEF4F89D4B66335C1C7E4248367D8ED9612EC453902D8E50AF89D7709D1A596C1F41F95AA82CA6C49AE90CD1668BAAC7AA6F2B4A8CA99B2C2372ACB08CF61C9C3805E"), + fromHex("3E60F958F89E79DF1E70ECF03CE6244A71D892D1855833296E4B245FA3CC18F688F97D9E44AF7CD887BB95DB93C34DE08CAC4F6CC5E41E53F7733BFF48C19C12F06B00EA2517D735A5F939B89B908C281B9121A6C1B26CA6C5465FC0BDCD07CA0C9284A9014BF58395875A4BCFFA523131E84F77D288FEFCEE1B4D4229FB0F31075573250C08ED6A5870C6E3779DF375F869401B4C4ABD4407011EA2F5540A7E572AC2EDAD80F94C5D35C322D2C2934F305B2CDB31B6B890595C80464AA5B32721FBB204EF72AC1F11384318D73D3C79D05A9946103416C4881374182E4D569EC2729610D02993D8888985EB4D1334449D5421ED3E9FE81EFDC5B2F28863F8E480CAB18845D48A436FBA35C1A920443ACA8DD4A5F0D88F0E10C87E45409C28654CB5E630F173453AF4D4540AA85DB48A242B779FBCF6F602700EE165AF5D8775DB665CFA858F92F66B471220C117A5A9A3B51F6C7DE5E41C8551ED101930A57FE38D81F36E7457D0F447023C8A09D97E30760AA0317559C5180239C616539164433A10D6DB2D2D69D23B8506E5D6D9B51B7501458F760B8D17B49324A9931D2A6911B75CFB4AADD58AF1928F723E939BEE1048DA8CA8F1FE7749FDDCE1F707AF151BB88431695926FD81F871A0148C63BA2BD7B369D8C797C71FD86D4F5572820204037132A2BED192FBB4D88EDF6FBD12D43D91E8363EF026056E684A8EB9B5"), + }, + +} + +func Test_Check(t *testing.T) { + for _, tt := range cipTests { + c, err := NewCipher(tt.key) + if err != nil { + t.Fatal(err) + } + + b := make([]byte, len(tt.plain)) + cipher.NewECBEncrypter(c). + CryptBlocks(b[:], tt.plain) + if !bytes.Equal(b[:], tt.cipher) { + t.Errorf("encrypt failed: got %x, want %x", b, tt.cipher) + } + + cipher.NewECBDecrypter(c). + CryptBlocks(b[:], tt.cipher) + if !bytes.Equal(b[:], tt.plain) { + t.Errorf("decrypt failed: got %x, want %x", b, tt.plain) + } + } +} diff --git a/cipher/shacal2/utils.go b/cipher/shacal2/utils.go new file mode 100644 index 00000000..6c20b970 --- /dev/null +++ b/cipher/shacal2/utils.go @@ -0,0 +1,90 @@ +package shacal2 + +import ( + "math/bits" + "encoding/binary" +) + +func getu32(ptr []byte) uint32 { + return binary.BigEndian.Uint32(ptr) +} + +func putu32(ptr []byte, a uint32) { + binary.BigEndian.PutUint32(ptr, a) +} + +func bytesToUint32s(b []byte) []uint32 { + size := len(b) / 4 + dst := make([]uint32, size) + + for i := 0; i < size; i++ { + j := i * 4 + + dst[i] = binary.BigEndian.Uint32(b[j:]) + } + + return dst +} + +func uint32sToBytes(w []uint32) []byte { + size := len(w) * 4 + dst := make([]byte, size) + + for i := 0; i < len(w); i++ { + j := i * 4 + + binary.BigEndian.PutUint32(dst[j:], w[i]) + } + + return dst +} + +func rotr(x uint32, n int) uint32 { + return bits.RotateLeft32(x, 32 - n) +} + +func sigma(x uint32, r1, r2, s int) uint32 { + return rotr(x, r1) ^ rotr(x, r2) ^ (x >> s) +} + +func rho(x uint32, r1, r2, r3 int) uint32 { + return rotr(x, r1) ^ rotr(x, r2) ^ rotr(x, r3) +} + +func choose(mask, a, b uint32) uint32 { + return b ^ (mask & (a ^ b)) +} + +func majority(a, b, c uint32) uint32 { + return choose(a ^ b, c, b) +} + +func fwd( + A, B, C uint32, + D *uint32, + E, F, G uint32, + H *uint32, + RK uint32, +) { + A_rho := rho(A, 2, 13, 22) + E_rho := rho(E, 6, 11, 25) + + (*H) += E_rho + choose(E, F, G) + RK + (*D) += (*H) + (*H) += A_rho + majority(A, B, C) +} + +func rev( + A, B, C uint32, + D *uint32, + E, F, G uint32, + H *uint32, + RK uint32, +) { + A_rho := rho(A, 2, 13, 22) + E_rho := rho(E, 6, 11, 25) + + (*H) -= A_rho + majority(A, B, C) + (*D) -= (*H) + (*H) -= E_rho + choose(E, F, G) + RK +} diff --git a/tool/math/gcd/extended.go b/tool/math/gcd/extended.go new file mode 100644 index 00000000..6503180f --- /dev/null +++ b/tool/math/gcd/extended.go @@ -0,0 +1,10 @@ +package gcd + +// Extended simple extended gcd +func Extended(a, b int64) (int64, int64, int64) { + if a == 0 { + return b, 0, 1 + } + gcd, xPrime, yPrime := Extended(b%a, a) + return gcd, yPrime - (b/a)*xPrime, xPrime +} diff --git a/tool/math/gcd/extended_test.go b/tool/math/gcd/extended_test.go new file mode 100644 index 00000000..e9f42b31 --- /dev/null +++ b/tool/math/gcd/extended_test.go @@ -0,0 +1,24 @@ +package gcd + +import "testing" + +func TestExtended(t *testing.T) { + var testCasesExtended = []struct { + name string + a int64 + b int64 + gcd int64 + x int64 + y int64 + }{ + {"gcd of 30 and 50", 30, 50, 10, 2, -1}, + } + for _, tc := range testCasesExtended { + t.Run(tc.name, func(t *testing.T) { + gcd, x, y := Extended(tc.a, tc.b) + if gcd != tc.gcd && x != tc.x && y != tc.y { + t.Fatalf("Expected values:\n\tGCD: Expected %v Returned %v,\n\tx: Expected %v Returned %v\n\ty: Expected %v Returned %v", tc.gcd, gcd, tc.x, x, tc.y, y) + } + }) + } +} diff --git a/tool/math/gcd/extendedgcd.go b/tool/math/gcd/extendedgcd.go new file mode 100644 index 00000000..5db9a9c8 --- /dev/null +++ b/tool/math/gcd/extendedgcd.go @@ -0,0 +1,12 @@ +package gcd + +// ExtendedRecursive finds and returns gcd(a, b), x, y satisfying a*x + b*y = gcd(a, b). +func ExtendedRecursive(a, b int64) (int64, int64, int64) { + if b > 0 { + d, y, x := ExtendedRecursive(b, a%b) + y -= (a / b) * x + return d, x, y + } + + return a, 1, 0 +} diff --git a/tool/math/gcd/extendedgcd_test.go b/tool/math/gcd/extendedgcd_test.go new file mode 100644 index 00000000..420f29b8 --- /dev/null +++ b/tool/math/gcd/extendedgcd_test.go @@ -0,0 +1,56 @@ +package gcd + +import "testing" + +type testExtendedFunction func(int64, int64) (int64, int64, int64) + +func TemplateTestExtendedGCD(t *testing.T, f testExtendedFunction) { + var testCasesExtended = []struct { + name string + a int64 + b int64 + gcd int64 + x int64 + y int64 + }{ + {"gcd of 10 and 0", 10, 0, 10, 1, 0}, + {"gcd of 98 and 56", 98, 56, 14, -1, 2}, + {"gcd of 0 and 10", 0, 10, 10, 0, 1}, + } + for _, tc := range testCasesExtended { + t.Run(tc.name, func(t *testing.T) { + actualGcd, actualX, actualY := f(tc.a, tc.b) + if actualGcd != tc.gcd { + t.Errorf("Expected GCD of %d and %d to be: %v, but got: %d", tc.a, tc.b, tc.gcd, actualGcd) + } + if actualX != tc.x { + t.Errorf("Expected x satisfying %d * x + %d * y = gcd to be: %v, but got: %d", tc.a, tc.b, tc.x, actualX) + } + if actualY != tc.y { + t.Errorf("Expected y satisfying %d * x + %d * y = gcd to be: %v, but got: %d", tc.a, tc.b, tc.y, actualY) + } + }) + } +} + +func TestExtendedGCDRecursive(t *testing.T) { + TemplateTestExtendedGCD(t, ExtendedRecursive) +} + +func TestExtendedGCDIterative(t *testing.T) { + TemplateTestExtendedGCD(t, ExtendedIterative) +} + +func TemplateBenchmarkExtendedGCD(b *testing.B, f testExtendedFunction) { + for i := 0; i < b.N; i++ { + f(98, 56) + } +} + +func BenchmarkExtendedGCDRecursive(b *testing.B) { + TemplateBenchmarkExtendedGCD(b, ExtendedRecursive) +} + +func BenchmarkExtendedGCDIterative(b *testing.B) { + TemplateBenchmarkExtendedGCD(b, ExtendedIterative) +} diff --git a/tool/math/gcd/extendedgcditerative.go b/tool/math/gcd/extendedgcditerative.go new file mode 100644 index 00000000..985d26d2 --- /dev/null +++ b/tool/math/gcd/extendedgcditerative.go @@ -0,0 +1,13 @@ +package gcd + +// ExtendedIterative finds and returns gcd(a, b), x, y satisfying a*x + b*y = gcd(a, b). +func ExtendedIterative(a, b int64) (int64, int64, int64) { + var u, y, v, x int64 = 1, 1, 0, 0 + for a > 0 { + var q int64 = b / a + x, u = u, x-q*u + y, v = v, y-q*v + b, a = a, b-q*a + } + return b, x, y +} diff --git a/tool/math/gcd/gcd.go b/tool/math/gcd/gcd.go new file mode 100644 index 00000000..1f808991 --- /dev/null +++ b/tool/math/gcd/gcd.go @@ -0,0 +1,9 @@ +package gcd + +// Recursive finds and returns the greatest common divisor of a given integer. +func Recursive(a, b int64) int64 { + if b == 0 { + return a + } + return Recursive(b, a%b) +} diff --git a/tool/math/gcd/gcd_test.go b/tool/math/gcd/gcd_test.go new file mode 100644 index 00000000..79358c51 --- /dev/null +++ b/tool/math/gcd/gcd_test.go @@ -0,0 +1,49 @@ +package gcd + +import "testing" + +type testFunction func(int64, int64) int64 + +var testCases = []struct { + name string + a int64 + b int64 + output int64 +}{ + {"gcd of 10 and 0", 10, 0, 10}, + {"gcd of 98 and 56", 98, 56, 14}, + {"gcd of 0 and 10", 0, 10, 10}, +} + +func TemplateTestGCD(t *testing.T, f testFunction) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual := f(tc.a, tc.b) + if actual != tc.output { + t.Errorf("Expected GCD of %d and %d to be: %v, but got: %d", tc.a, tc.b, tc.output, actual) + } + }) + } +} + +func TestGCDRecursive(t *testing.T) { + TemplateTestGCD(t, Recursive) +} + +func TestGCDIterative(t *testing.T) { + TemplateTestGCD(t, Iterative) +} + +func TemplateBenchmarkGCD(b *testing.B, f testFunction) { + for i := 0; i < b.N; i++ { + f(98, 56) + } +} + +func BenchmarkGCDRecursive(b *testing.B) { + TemplateBenchmarkGCD(b, Recursive) +} + +func BenchmarkGCDIterative(b *testing.B) { + TemplateBenchmarkGCD(b, Iterative) +} diff --git a/tool/math/gcd/gcditerative.go b/tool/math/gcd/gcditerative.go new file mode 100644 index 00000000..65c299ac --- /dev/null +++ b/tool/math/gcd/gcditerative.go @@ -0,0 +1,9 @@ +package gcd + +// Iterative Faster iterative version of GcdRecursive without holding up too much of the stack +func Iterative(a, b int64) int64 { + for b != 0 { + a, b = b, a%b + } + return a +} diff --git a/tool/math/lcm/lcm.go b/tool/math/lcm/lcm.go new file mode 100644 index 00000000..9d02a52d --- /dev/null +++ b/tool/math/lcm/lcm.go @@ -0,0 +1,12 @@ +package lcm + +import ( + "math" + + "github.com/deatil/go-cryptobin/tool/math/gcd" +) + +// Lcm returns the lcm of two numbers using the fact that lcm(a,b) * gcd(a,b) = | a * b | +func Lcm(a, b int64) int64 { + return int64(math.Abs(float64(a*b)) / float64(gcd.Iterative(a, b))) +} diff --git a/tool/math/lcm/lcm_test.go b/tool/math/lcm/lcm_test.go new file mode 100644 index 00000000..dcedc400 --- /dev/null +++ b/tool/math/lcm/lcm_test.go @@ -0,0 +1,44 @@ +package lcm + +import "testing" + +func Test_Lcm(t *testing.T) { + testCases := []struct { + name string + a int64 + b int64 + output int64 + }{ + { + name: "LCM of 1 & 5", + a: 1, + b: 5, + output: 5, + }, { + name: "LCM of 2 & 5", + a: 2, + b: 5, + output: 10, + }, { + name: "LCM of 5 & 10", + a: 10, + b: 5, + output: 10, + }, { + name: "LCM of 5 & 5", + a: 5, + b: 5, + output: 5, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual_output := Lcm(tc.a, tc.b) + if actual_output != tc.output { + t.Errorf("Expected LCM of %d and %d is %d, but got %d", tc.a, tc.b, tc.output, + actual_output) + } + }) + } +}