diff --git a/cng/rc4.go b/cng/rc4.go new file mode 100644 index 0000000..e0d4507 --- /dev/null +++ b/cng/rc4.go @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//go:build windows +// +build windows + +package cng + +import ( + "runtime" + + "github.com/microsoft/go-crypto-winnative/internal/bcrypt" + "github.com/microsoft/go-crypto-winnative/internal/subtle" +) + +// A RC4Cipher is an instance of RC4 using a particular key. +type RC4Cipher struct { + kh bcrypt.KEY_HANDLE +} + +// NewRC4Cipher creates and returns a new Cipher. +func NewRC4Cipher(key []byte) (*RC4Cipher, error) { + kh, err := newCipherHandle(bcrypt.RC4_ALGORITHM, "", key) + if err != nil { + return nil, err + } + c := &RC4Cipher{kh: kh} + runtime.SetFinalizer(c, (*RC4Cipher).finalize) + return c, nil +} + +func (c *RC4Cipher) finalize() { + if c.kh != 0 { + bcrypt.DestroyKey(c.kh) + } +} + +// Reset zeros the key data and makes the Cipher unusable. +func (c *RC4Cipher) Reset() { + bcrypt.DestroyKey(c.kh) + c.kh = 0 +} + +// XORKeyStream sets dst to the result of XORing src with the key stream. +// Dst and src must overlap entirely or not at all. +func (c *RC4Cipher) XORKeyStream(dst, src []byte) { + if c.kh == 0 || len(src) == 0 { + return + } + if subtle.InexactOverlap(dst[:len(src)], src) { + panic("crypto/rc4: invalid buffer overlap") + } + var outLen uint32 + if err := bcrypt.Encrypt(c.kh, src, nil, nil, dst, &outLen, 0); err != nil { + panic("crypto/rc4: encryption failed: " + err.Error()) + } + if int(outLen) != len(src) { + panic("crypto/rc4: src not fully XORed") + } + runtime.KeepAlive(c) +} diff --git a/cng/rc4_test.go b/cng/rc4_test.go new file mode 100644 index 0000000..3920b15 --- /dev/null +++ b/cng/rc4_test.go @@ -0,0 +1,164 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//go:build windows +// +build windows + +package cng_test + +import ( + "bytes" + "fmt" + "testing" + + "github.com/microsoft/go-crypto-winnative/cng" +) + +type rc4Test struct { + key, keystream []byte +} + +var golden = []rc4Test{ + // Test vectors from the original cypherpunk posting of ARC4: + // https://groups.google.com/group/sci.crypt/msg/10a300c9d21afca0?pli=1 + { + []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + []byte{0x74, 0x94, 0xc2, 0xe7, 0x10, 0x4b, 0x08, 0x79}, + }, + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + []byte{0xde, 0x18, 0x89, 0x41, 0xa3, 0x37, 0x5d, 0x3a}, + }, + { + []byte{0xef, 0x01, 0x23, 0x45}, + []byte{0xd6, 0xa1, 0x41, 0xa7, 0xec, 0x3c, 0x38, 0xdf, 0xbd, 0x61}, + }, + + // Test vectors from the Wikipedia page: https://en.wikipedia.org/wiki/RC4 + { + []byte{0x4b, 0x65, 0x79}, + []byte{0xeb, 0x9f, 0x77, 0x81, 0xb7, 0x34, 0xca, 0x72, 0xa7, 0x19}, + }, + { + []byte{0x57, 0x69, 0x6b, 0x69}, + []byte{0x60, 0x44, 0xdb, 0x6d, 0x41, 0xb7}, + }, + { + []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + []byte{ + 0xde, 0x18, 0x89, 0x41, 0xa3, 0x37, 0x5d, 0x3a, + 0x8a, 0x06, 0x1e, 0x67, 0x57, 0x6e, 0x92, 0x6d, + 0xc7, 0x1a, 0x7f, 0xa3, 0xf0, 0xcc, 0xeb, 0x97, + 0x45, 0x2b, 0x4d, 0x32, 0x27, 0x96, 0x5f, 0x9e, + 0xa8, 0xcc, 0x75, 0x07, 0x6d, 0x9f, 0xb9, 0xc5, + 0x41, 0x7a, 0xa5, 0xcb, 0x30, 0xfc, 0x22, 0x19, + 0x8b, 0x34, 0x98, 0x2d, 0xbb, 0x62, 0x9e, 0xc0, + 0x4b, 0x4f, 0x8b, 0x05, 0xa0, 0x71, 0x08, 0x50, + 0x92, 0xa0, 0xc3, 0x58, 0x4a, 0x48, 0xe4, 0xa3, + 0x0a, 0x39, 0x7b, 0x8a, 0xcd, 0x1d, 0x00, 0x9e, + 0xc8, 0x7d, 0x68, 0x11, 0xf2, 0x2c, 0xf4, 0x9c, + 0xa3, 0xe5, 0x93, 0x54, 0xb9, 0x45, 0x15, 0x35, + 0xa2, 0x18, 0x7a, 0x86, 0x42, 0x6c, 0xca, 0x7d, + 0x5e, 0x82, 0x3e, 0xba, 0x00, 0x44, 0x12, 0x67, + 0x12, 0x57, 0xb8, 0xd8, 0x60, 0xae, 0x4c, 0xbd, + 0x4c, 0x49, 0x06, 0xbb, 0xc5, 0x35, 0xef, 0xe1, + 0x58, 0x7f, 0x08, 0xdb, 0x33, 0x95, 0x5c, 0xdb, + 0xcb, 0xad, 0x9b, 0x10, 0xf5, 0x3f, 0xc4, 0xe5, + 0x2c, 0x59, 0x15, 0x65, 0x51, 0x84, 0x87, 0xfe, + 0x08, 0x4d, 0x0e, 0x3f, 0x03, 0xde, 0xbc, 0xc9, + 0xda, 0x1c, 0xe9, 0x0d, 0x08, 0x5c, 0x2d, 0x8a, + 0x19, 0xd8, 0x37, 0x30, 0x86, 0x16, 0x36, 0x92, + 0x14, 0x2b, 0xd8, 0xfc, 0x5d, 0x7a, 0x73, 0x49, + 0x6a, 0x8e, 0x59, 0xee, 0x7e, 0xcf, 0x6b, 0x94, + 0x06, 0x63, 0xf4, 0xa6, 0xbe, 0xe6, 0x5b, 0xd2, + 0xc8, 0x5c, 0x46, 0x98, 0x6c, 0x1b, 0xef, 0x34, + 0x90, 0xd3, 0x7b, 0x38, 0xda, 0x85, 0xd3, 0x2e, + 0x97, 0x39, 0xcb, 0x23, 0x4a, 0x2b, 0xe7, 0x40, + }, + }, +} + +func TestRC4Golden(t *testing.T) { + for gi, g := range golden { + data := make([]byte, len(g.keystream)) + for i := range data { + data[i] = byte(i) + } + + expect := make([]byte, len(g.keystream)) + for i := range expect { + expect[i] = byte(i) ^ g.keystream[i] + } + + for size := 1; size <= len(g.keystream); size++ { + c, err := cng.NewRC4Cipher(g.key) + if err != nil { + t.Fatalf("#%d: NewCipher: %v", gi, err) + } + + off := 0 + for off < len(g.keystream) { + n := len(g.keystream) - off + if n > size { + n = size + } + desc := fmt.Sprintf("#%d@[%d:%d]", gi, off, off+n) + src := data[off : off+n] + expect := expect[off : off+n] + dst := make([]byte, len(src)) + c.XORKeyStream(dst, src) + for i, v := range dst { + if v != expect[i] { + t.Fatalf("%s: mismatch at byte %d:\nhave %x\nwant %x", desc, i, dst, expect) + } + } + off += n + } + } + } +} + +func TestRC4Block(t *testing.T) { + c1a, _ := cng.NewRC4Cipher(golden[0].key) + c1b, _ := cng.NewRC4Cipher(golden[1].key) + data1 := make([]byte, 1<<20) + for i := range data1 { + c1a.XORKeyStream(data1[i:i+1], data1[i:i+1]) + c1b.XORKeyStream(data1[i:i+1], data1[i:i+1]) + } + + c2a, _ := cng.NewRC4Cipher(golden[0].key) + c2b, _ := cng.NewRC4Cipher(golden[1].key) + data2 := make([]byte, 1<<20) + c2a.XORKeyStream(data2, data2) + c2b.XORKeyStream(data2, data2) + + if !bytes.Equal(data1, data2) { + t.Fatalf("bad block") + } +} + +func benchmarkRC4(b *testing.B, size int64) { + buf := make([]byte, size) + c, err := cng.NewRC4Cipher(golden[0].key) + if err != nil { + panic(err) + } + b.SetBytes(size) + + for i := 0; i < b.N; i++ { + c.XORKeyStream(buf, buf) + } +} + +func BenchmarkRC4_128(b *testing.B) { + benchmarkRC4(b, 128) +} + +func BenchmarkRC4_1K(b *testing.B) { + benchmarkRC4(b, 1024) +} + +func BenchmarkRC4_8K(b *testing.B) { + benchmarkRC4(b, 8096) +} diff --git a/internal/bcrypt/bcrypt_windows.go b/internal/bcrypt/bcrypt_windows.go index 829ec66..37c64ba 100644 --- a/internal/bcrypt/bcrypt_windows.go +++ b/internal/bcrypt/bcrypt_windows.go @@ -19,6 +19,7 @@ const ( SHA3_384_ALGORITHM = "SHA3-384" SHA3_512_ALGORITHM = "SHA3-512" AES_ALGORITHM = "AES" + RC4_ALGORITHM = "RC4" RSA_ALGORITHM = "RSA" MD4_ALGORITHM = "MD4" MD5_ALGORITHM = "MD5"