Skip to content
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

Add support for RC4 #46

Merged
merged 3 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions cng/rc4.go
Original file line number Diff line number Diff line change
@@ -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)
}
164 changes: 164 additions & 0 deletions cng/rc4_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 1 addition & 0 deletions internal/bcrypt/bcrypt_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading