Skip to content

Commit 78dacc3

Browse files
authored
Merge pull request #117 from golang-fips/rc4
Add support for RC4
2 parents 251d5fd + bc4ab9f commit 78dacc3

File tree

5 files changed

+261
-4
lines changed

5 files changed

+261
-4
lines changed

cgo_go122.go

+2
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ package openssl
99
// functions that are known to allocate.
1010
#cgo noescape go_openssl_EVP_PKEY_derive
1111
#cgo nocallback go_openssl_EVP_PKEY_derive
12+
#cgo noescape go_openssl_EVP_EncryptUpdate
13+
#cgo nocallback go_openssl_EVP_EncryptUpdate
1214
*/
1315
import "C"

cipher.go

+27-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const (
2222
cipherAES256
2323
cipherDES
2424
cipherDES3
25+
cipherRC4
2526
)
2627

2728
func (c cipherKind) String() string {
@@ -36,6 +37,8 @@ func (c cipherKind) String() string {
3637
return "DES"
3738
case cipherDES3:
3839
return "DES3"
40+
case cipherRC4:
41+
return "RC4"
3942
default:
4043
panic("unknown cipher kind: " + strconv.Itoa(int(c)))
4144
}
@@ -44,7 +47,8 @@ func (c cipherKind) String() string {
4447
type cipherMode int8
4548

4649
const (
47-
cipherModeECB cipherMode = iota
50+
cipherModeNone cipherMode = -1
51+
cipherModeECB cipherMode = iota
4852
cipherModeCBC
4953
cipherModeCTR
5054
cipherModeGCM
@@ -133,6 +137,8 @@ func loadCipher(k cipherKind, mode cipherMode) (cipher C.GO_EVP_CIPHER_PTR) {
133137
case cipherModeCBC:
134138
cipher = C.go_openssl_EVP_des_ede3_cbc()
135139
}
140+
case cipherRC4:
141+
cipher = C.go_openssl_EVP_rc4()
136142
}
137143
return cipher
138144
}
@@ -479,17 +485,34 @@ func sliceForAppend(in []byte, n int) (head, tail []byte) {
479485
return
480486
}
481487

482-
func newCipherCtx(kind cipherKind, mode cipherMode, encrypt cipherOp, key, iv []byte) (C.GO_EVP_CIPHER_CTX_PTR, error) {
488+
func newCipherCtx(kind cipherKind, mode cipherMode, encrypt cipherOp, key, iv []byte) (ctx C.GO_EVP_CIPHER_CTX_PTR, err error) {
483489
cipher := loadCipher(kind, mode)
484490
if cipher == nil {
485491
panic("crypto/cipher: unsupported cipher: " + kind.String())
486492
}
487-
ctx := C.go_openssl_EVP_CIPHER_CTX_new()
493+
ctx = C.go_openssl_EVP_CIPHER_CTX_new()
488494
if ctx == nil {
489495
return nil, fail("unable to create EVP cipher ctx")
490496
}
497+
defer func() {
498+
if err != nil {
499+
C.go_openssl_EVP_CIPHER_CTX_free(ctx)
500+
}
501+
}()
502+
if kind == cipherRC4 {
503+
// RC4 cipher supports a variable key length.
504+
// We need to set the key length before setting the key,
505+
// and to do so we need to have an initialized cipher ctx.
506+
if C.go_openssl_EVP_CipherInit_ex(ctx, cipher, nil, nil, nil, C.int(encrypt)) != 1 {
507+
return nil, newOpenSSLError("EVP_CipherInit_ex")
508+
}
509+
if C.go_openssl_EVP_CIPHER_CTX_set_key_length(ctx, C.int(len(key))) != 1 {
510+
return nil, newOpenSSLError("EVP_CIPHER_CTX_set_key_length")
511+
}
512+
// Pass nil to the next call to EVP_CipherInit_ex to avoid resetting ctx's cipher.
513+
cipher = nil
514+
}
491515
if C.go_openssl_EVP_CipherInit_ex(ctx, cipher, nil, base(key), base(iv), C.int(encrypt)) != 1 {
492-
C.go_openssl_EVP_CIPHER_CTX_free(ctx)
493516
return nil, fail("unable to initialize EVP cipher ctx")
494517
}
495518
return ctx, nil

rc4.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//go:build !cmd_go_bootstrap
2+
3+
package openssl
4+
5+
// #include "goopenssl.h"
6+
import "C"
7+
import "runtime"
8+
9+
// SupportsRC4 returns true if NewRC4Cipher is supported.
10+
func SupportsRC4() bool {
11+
// True for stock OpenSSL 1.
12+
// False for stock OpenSSL 3 unless the legacy provider is available.
13+
return loadCipher(cipherRC4, cipherModeNone) != nil
14+
}
15+
16+
// A RC4Cipher is an instance of RC4 using a particular key.
17+
type RC4Cipher struct {
18+
ctx C.GO_EVP_CIPHER_CTX_PTR
19+
}
20+
21+
// NewRC4Cipher creates and returns a new Cipher.
22+
func NewRC4Cipher(key []byte) (*RC4Cipher, error) {
23+
ctx, err := newCipherCtx(cipherRC4, cipherModeNone, cipherOpEncrypt, key, nil)
24+
if err != nil {
25+
return nil, err
26+
}
27+
c := &RC4Cipher{ctx}
28+
runtime.SetFinalizer(c, (*RC4Cipher).finalize)
29+
return c, nil
30+
}
31+
32+
func (c *RC4Cipher) finalize() {
33+
if c.ctx != nil {
34+
C.go_openssl_EVP_CIPHER_CTX_free(c.ctx)
35+
}
36+
}
37+
38+
// Reset zeros the key data and makes the Cipher unusable.
39+
func (c *RC4Cipher) Reset() {
40+
if c.ctx != nil {
41+
C.go_openssl_EVP_CIPHER_CTX_free(c.ctx)
42+
c.ctx = nil
43+
}
44+
}
45+
46+
// XORKeyStream sets dst to the result of XORing src with the key stream.
47+
// Dst and src must overlap entirely or not at all.
48+
func (c *RC4Cipher) XORKeyStream(dst, src []byte) {
49+
if c.ctx == nil || len(src) == 0 {
50+
return
51+
}
52+
if inexactOverlap(dst[:len(src)], src) {
53+
panic("crypto/rc4: invalid buffer overlap")
54+
}
55+
var outLen C.int
56+
if C.go_openssl_EVP_EncryptUpdate(c.ctx, base(dst), &outLen, base(src), C.int(len(src))) != 1 {
57+
panic("crypto/cipher: EncryptUpdate failed")
58+
}
59+
if int(outLen) != len(src) {
60+
panic("crypto/rc4: src not fully XORed")
61+
}
62+
runtime.KeepAlive(c)
63+
}

rc4_test.go

+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package openssl_test
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"testing"
7+
8+
"github.com/golang-fips/openssl/v2"
9+
)
10+
11+
type rc4Test struct {
12+
key, keystream []byte
13+
}
14+
15+
var golden = []rc4Test{
16+
// Test vectors from the original cypherpunk posting of ARC4:
17+
// https://groups.google.com/group/sci.crypt/msg/10a300c9d21afca0?pli=1
18+
{
19+
[]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
20+
[]byte{0x74, 0x94, 0xc2, 0xe7, 0x10, 0x4b, 0x08, 0x79},
21+
},
22+
{
23+
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
24+
[]byte{0xde, 0x18, 0x89, 0x41, 0xa3, 0x37, 0x5d, 0x3a},
25+
},
26+
{
27+
[]byte{0xef, 0x01, 0x23, 0x45},
28+
[]byte{0xd6, 0xa1, 0x41, 0xa7, 0xec, 0x3c, 0x38, 0xdf, 0xbd, 0x61},
29+
},
30+
31+
// Test vectors from the Wikipedia page: https://en.wikipedia.org/wiki/RC4
32+
{
33+
[]byte{0x4b, 0x65, 0x79},
34+
[]byte{0xeb, 0x9f, 0x77, 0x81, 0xb7, 0x34, 0xca, 0x72, 0xa7, 0x19},
35+
},
36+
{
37+
[]byte{0x57, 0x69, 0x6b, 0x69},
38+
[]byte{0x60, 0x44, 0xdb, 0x6d, 0x41, 0xb7},
39+
},
40+
{
41+
[]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
42+
[]byte{
43+
0xde, 0x18, 0x89, 0x41, 0xa3, 0x37, 0x5d, 0x3a,
44+
0x8a, 0x06, 0x1e, 0x67, 0x57, 0x6e, 0x92, 0x6d,
45+
0xc7, 0x1a, 0x7f, 0xa3, 0xf0, 0xcc, 0xeb, 0x97,
46+
0x45, 0x2b, 0x4d, 0x32, 0x27, 0x96, 0x5f, 0x9e,
47+
0xa8, 0xcc, 0x75, 0x07, 0x6d, 0x9f, 0xb9, 0xc5,
48+
0x41, 0x7a, 0xa5, 0xcb, 0x30, 0xfc, 0x22, 0x19,
49+
0x8b, 0x34, 0x98, 0x2d, 0xbb, 0x62, 0x9e, 0xc0,
50+
0x4b, 0x4f, 0x8b, 0x05, 0xa0, 0x71, 0x08, 0x50,
51+
0x92, 0xa0, 0xc3, 0x58, 0x4a, 0x48, 0xe4, 0xa3,
52+
0x0a, 0x39, 0x7b, 0x8a, 0xcd, 0x1d, 0x00, 0x9e,
53+
0xc8, 0x7d, 0x68, 0x11, 0xf2, 0x2c, 0xf4, 0x9c,
54+
0xa3, 0xe5, 0x93, 0x54, 0xb9, 0x45, 0x15, 0x35,
55+
0xa2, 0x18, 0x7a, 0x86, 0x42, 0x6c, 0xca, 0x7d,
56+
0x5e, 0x82, 0x3e, 0xba, 0x00, 0x44, 0x12, 0x67,
57+
0x12, 0x57, 0xb8, 0xd8, 0x60, 0xae, 0x4c, 0xbd,
58+
0x4c, 0x49, 0x06, 0xbb, 0xc5, 0x35, 0xef, 0xe1,
59+
0x58, 0x7f, 0x08, 0xdb, 0x33, 0x95, 0x5c, 0xdb,
60+
0xcb, 0xad, 0x9b, 0x10, 0xf5, 0x3f, 0xc4, 0xe5,
61+
0x2c, 0x59, 0x15, 0x65, 0x51, 0x84, 0x87, 0xfe,
62+
0x08, 0x4d, 0x0e, 0x3f, 0x03, 0xde, 0xbc, 0xc9,
63+
0xda, 0x1c, 0xe9, 0x0d, 0x08, 0x5c, 0x2d, 0x8a,
64+
0x19, 0xd8, 0x37, 0x30, 0x86, 0x16, 0x36, 0x92,
65+
0x14, 0x2b, 0xd8, 0xfc, 0x5d, 0x7a, 0x73, 0x49,
66+
0x6a, 0x8e, 0x59, 0xee, 0x7e, 0xcf, 0x6b, 0x94,
67+
0x06, 0x63, 0xf4, 0xa6, 0xbe, 0xe6, 0x5b, 0xd2,
68+
0xc8, 0x5c, 0x46, 0x98, 0x6c, 0x1b, 0xef, 0x34,
69+
0x90, 0xd3, 0x7b, 0x38, 0xda, 0x85, 0xd3, 0x2e,
70+
0x97, 0x39, 0xcb, 0x23, 0x4a, 0x2b, 0xe7, 0x40,
71+
},
72+
},
73+
}
74+
75+
func TestRC4Golden(t *testing.T) {
76+
if !openssl.SupportsRC4() {
77+
t.Skip("RC4 is not supported")
78+
}
79+
for gi, g := range golden {
80+
data := make([]byte, len(g.keystream))
81+
for i := range data {
82+
data[i] = byte(i)
83+
}
84+
85+
expect := make([]byte, len(g.keystream))
86+
for i := range expect {
87+
expect[i] = byte(i) ^ g.keystream[i]
88+
}
89+
90+
for size := 1; size <= len(g.keystream); size++ {
91+
c, err := openssl.NewRC4Cipher(g.key)
92+
if err != nil {
93+
t.Fatalf("#%d: NewCipher: %v", gi, err)
94+
}
95+
96+
off := 0
97+
for off < len(g.keystream) {
98+
n := len(g.keystream) - off
99+
if n > size {
100+
n = size
101+
}
102+
desc := fmt.Sprintf("#%d@[%d:%d]", gi, off, off+n)
103+
src := data[off : off+n]
104+
expect := expect[off : off+n]
105+
dst := make([]byte, len(src))
106+
c.XORKeyStream(dst, src)
107+
for i, v := range dst {
108+
if v != expect[i] {
109+
t.Fatalf("%s: mismatch at byte %d:\nhave %x\nwant %x", desc, i, dst, expect)
110+
}
111+
}
112+
off += n
113+
}
114+
}
115+
}
116+
}
117+
118+
func TestRC4Block(t *testing.T) {
119+
if !openssl.SupportsRC4() {
120+
t.Skip("RC4 is not supported")
121+
}
122+
c1a, _ := openssl.NewRC4Cipher(golden[0].key)
123+
c1b, _ := openssl.NewRC4Cipher(golden[1].key)
124+
data1 := make([]byte, 1<<20)
125+
for i := range data1 {
126+
c1a.XORKeyStream(data1[i:i+1], data1[i:i+1])
127+
c1b.XORKeyStream(data1[i:i+1], data1[i:i+1])
128+
}
129+
130+
c2a, _ := openssl.NewRC4Cipher(golden[0].key)
131+
c2b, _ := openssl.NewRC4Cipher(golden[1].key)
132+
data2 := make([]byte, 1<<20)
133+
c2a.XORKeyStream(data2, data2)
134+
c2b.XORKeyStream(data2, data2)
135+
136+
if !bytes.Equal(data1, data2) {
137+
t.Fatalf("bad block")
138+
}
139+
}
140+
141+
func benchmarkRC4(b *testing.B, size int64) {
142+
if !openssl.SupportsRC4() {
143+
b.Skip("RC4 is not supported")
144+
}
145+
buf := make([]byte, size)
146+
c, err := openssl.NewRC4Cipher(golden[0].key)
147+
if err != nil {
148+
panic(err)
149+
}
150+
b.SetBytes(size)
151+
152+
for i := 0; i < b.N; i++ {
153+
c.XORKeyStream(buf, buf)
154+
}
155+
}
156+
157+
func BenchmarkRC4_128(b *testing.B) {
158+
benchmarkRC4(b, 128)
159+
}
160+
161+
func BenchmarkRC4_1K(b *testing.B) {
162+
benchmarkRC4(b, 1024)
163+
}
164+
165+
func BenchmarkRC4_8K(b *testing.B) {
166+
benchmarkRC4(b, 8096)
167+
}

shims.h

+2
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ DEFINEFUNC(const GO_EVP_CIPHER_PTR, EVP_des_ecb, (void), ()) \
257257
DEFINEFUNC(const GO_EVP_CIPHER_PTR, EVP_des_cbc, (void), ()) \
258258
DEFINEFUNC(const GO_EVP_CIPHER_PTR, EVP_des_ede3_ecb, (void), ()) \
259259
DEFINEFUNC(const GO_EVP_CIPHER_PTR, EVP_des_ede3_cbc, (void), ()) \
260+
DEFINEFUNC(const GO_EVP_CIPHER_PTR, EVP_rc4, (void), ()) \
260261
DEFINEFUNC_RENAMED_3_0(int, EVP_CIPHER_get_block_size, EVP_CIPHER_block_size, (const GO_EVP_CIPHER_PTR cipher), (cipher)) \
262+
DEFINEFUNC(int, EVP_CIPHER_CTX_set_key_length, (GO_EVP_CIPHER_CTX_PTR x, int keylen), (x, keylen)) \
261263
DEFINEFUNC(void, EVP_CIPHER_CTX_free, (GO_EVP_CIPHER_CTX_PTR arg0), (arg0)) \
262264
DEFINEFUNC(int, EVP_CIPHER_CTX_ctrl, (GO_EVP_CIPHER_CTX_PTR ctx, int type, int arg, void *ptr), (ctx, type, arg, ptr)) \
263265
DEFINEFUNC(GO_EVP_PKEY_PTR, EVP_PKEY_new, (void), ()) \

0 commit comments

Comments
 (0)