Skip to content

Commit b29b5cd

Browse files
dagoodqmuntal
andauthored
Merge pull request #58 from microsoft/cipret (#59)
Update DES and 3DES to use ECB instead of CBC chaining mode (cherry picked from commit fdc07be) Co-authored-by: Quim Muntal <qmuntaldiaz@microsoft.com>
1 parent a968e40 commit b29b5cd

File tree

5 files changed

+294
-2
lines changed

5 files changed

+294
-2
lines changed

cng/des.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ type desCipher struct {
2323
}
2424

2525
func NewDESCipher(key []byte) (cipher.Block, error) {
26-
kh, err := newCipherHandle(bcrypt.DES_ALGORITHM, "", key)
26+
kh, err := newCipherHandle(bcrypt.DES_ALGORITHM, bcrypt.CHAIN_MODE_ECB, key)
2727
if err != nil {
2828
return nil, err
2929
}
@@ -34,7 +34,7 @@ func NewDESCipher(key []byte) (cipher.Block, error) {
3434
}
3535

3636
func NewTripleDESCipher(key []byte) (cipher.Block, error) {
37-
kh, err := newCipherHandle(bcrypt.DES3_ALGORITHM, "", key)
37+
kh, err := newCipherHandle(bcrypt.DES3_ALGORITHM, bcrypt.CHAIN_MODE_ECB, key)
3838
if err != nil {
3939
return nil, err
4040
}

cng/des_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"testing"
1313

1414
"github.com/microsoft/go-crypto-winnative/cng"
15+
"github.com/microsoft/go-crypto-winnative/internal/cryptotest"
1516
)
1617

1718
type CryptTest struct {
@@ -1669,6 +1670,17 @@ func TestTripleDESCBCDecryptSimple(t *testing.T) {
16691670
}
16701671
}
16711672

1673+
// Test DES against the general cipher.Block interface tester.
1674+
func TestDESBlock(t *testing.T) {
1675+
t.Run("DES", func(t *testing.T) {
1676+
cryptotest.TestBlock(t, 8, cng.NewDESCipher)
1677+
})
1678+
1679+
t.Run("TripleDES", func(t *testing.T) {
1680+
cryptotest.TestBlock(t, 24, cng.NewTripleDESCipher)
1681+
})
1682+
}
1683+
16721684
func BenchmarkEncrypt(b *testing.B) {
16731685
tt := encryptDESTests[0]
16741686
c, err := cng.NewDESCipher(tt.key)

internal/cryptotest/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
The `internal/cryptotest` package provides a set of tests for cryptographic primitives.
2+
3+
`internal/cryptotest` has been copied from the Go standard library's [crypo/internal/cryptotest](https://github.com/golang/go/tree/807e01db4840e25e4d98911b28a8fa54244b8dfa/src/crypto/internal/cryptotest)
4+
package and trimmed down to only include the tests that are relevant for the `openssl` package.
5+
6+
[1]: https://github.com/golang/go/tree/807e01db4840e25e4d98911b28a8fa54244b8dfa/src/crypto/internal/cryptotest

internal/cryptotest/block.go

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cryptotest
6+
7+
import (
8+
"bytes"
9+
"crypto/cipher"
10+
"testing"
11+
)
12+
13+
// This file is a copy of https://github.com/golang/go/blob/9e9b1f57c26a6d13fdaebef67136718b8042cdba/src/crypto/internal/cryptotest/block.go.
14+
15+
type MakeBlock func(key []byte) (cipher.Block, error)
16+
17+
// TestBlock performs a set of tests on cipher.Block implementations, checking
18+
// the documented requirements of BlockSize, Encrypt, and Decrypt.
19+
func TestBlock(t *testing.T, keySize int, mb MakeBlock) {
20+
// Generate random key
21+
key := make([]byte, keySize)
22+
newRandReader(t).Read(key)
23+
t.Logf("Cipher key: 0x%x", key)
24+
25+
block, err := mb(key)
26+
if err != nil {
27+
t.Fatal(err)
28+
}
29+
30+
blockSize := block.BlockSize()
31+
32+
t.Run("Encryption", func(t *testing.T) {
33+
testCipher(t, block.Encrypt, blockSize)
34+
})
35+
36+
t.Run("Decryption", func(t *testing.T) {
37+
testCipher(t, block.Decrypt, blockSize)
38+
})
39+
40+
// Checks baseline Encrypt/Decrypt functionality. More thorough
41+
// implementation-specific characterization/golden tests should be done
42+
// for each block cipher implementation.
43+
t.Run("Roundtrip", func(t *testing.T) {
44+
rng := newRandReader(t)
45+
46+
// Check Decrypt inverts Encrypt
47+
before, ciphertext, after := make([]byte, blockSize), make([]byte, blockSize), make([]byte, blockSize)
48+
49+
rng.Read(before)
50+
51+
block.Encrypt(ciphertext, before)
52+
block.Decrypt(after, ciphertext)
53+
54+
if !bytes.Equal(after, before) {
55+
t.Errorf("plaintext is different after an encrypt/decrypt cycle; got %x, want %x", after, before)
56+
}
57+
58+
// Check Encrypt inverts Decrypt (assumes block ciphers are deterministic)
59+
before, plaintext, after := make([]byte, blockSize), make([]byte, blockSize), make([]byte, blockSize)
60+
61+
rng.Read(before)
62+
63+
block.Decrypt(plaintext, before)
64+
block.Encrypt(after, plaintext)
65+
66+
if !bytes.Equal(after, before) {
67+
t.Errorf("ciphertext is different after a decrypt/encrypt cycle; got %x, want %x", after, before)
68+
}
69+
})
70+
71+
}
72+
73+
func testCipher(t *testing.T, cipher func(dst, src []byte), blockSize int) {
74+
t.Run("AlterInput", func(t *testing.T) {
75+
rng := newRandReader(t)
76+
77+
// Make long src that shouldn't be modified at all, within block
78+
// size scope or beyond it
79+
src, before := make([]byte, blockSize*2), make([]byte, blockSize*2)
80+
rng.Read(src)
81+
copy(before, src)
82+
83+
dst := make([]byte, blockSize)
84+
85+
cipher(dst, src)
86+
if !bytes.Equal(src, before) {
87+
t.Errorf("block cipher modified src; got %x, want %x", src, before)
88+
}
89+
})
90+
91+
t.Run("Aliasing", func(t *testing.T) {
92+
rng := newRandReader(t)
93+
94+
buff, expectedOutput := make([]byte, blockSize), make([]byte, blockSize)
95+
96+
// Record what output is when src and dst are different
97+
rng.Read(buff)
98+
cipher(expectedOutput, buff)
99+
100+
// Check that the same output is generated when src=dst alias to the same
101+
// memory
102+
cipher(buff, buff)
103+
if !bytes.Equal(buff, expectedOutput) {
104+
t.Errorf("block cipher produced different output when dst = src; got %x, want %x", buff, expectedOutput)
105+
}
106+
})
107+
108+
t.Run("OutOfBoundsWrite", func(t *testing.T) {
109+
rng := newRandReader(t)
110+
111+
src := make([]byte, blockSize)
112+
rng.Read(src)
113+
114+
// Make a buffer with dst in the middle and data on either end
115+
buff := make([]byte, blockSize*3)
116+
endOfPrefix, startOfSuffix := blockSize, blockSize*2
117+
rng.Read(buff[:endOfPrefix])
118+
rng.Read(buff[startOfSuffix:])
119+
dst := buff[endOfPrefix:startOfSuffix]
120+
121+
// Record the prefix and suffix data to make sure they aren't written to
122+
initPrefix, initSuffix := make([]byte, blockSize), make([]byte, blockSize)
123+
copy(initPrefix, buff[:endOfPrefix])
124+
copy(initSuffix, buff[startOfSuffix:])
125+
126+
// Write to dst (the middle of the buffer) and make sure it doesn't write
127+
// beyond the dst slice
128+
cipher(dst, src)
129+
if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
130+
t.Errorf("block cipher did out of bounds write after end of dst slice; got %x, want %x", buff[startOfSuffix:], initSuffix)
131+
}
132+
if !bytes.Equal(buff[:endOfPrefix], initPrefix) {
133+
t.Errorf("block cipher did out of bounds write before beginning of dst slice; got %x, want %x", buff[:endOfPrefix], initPrefix)
134+
}
135+
136+
// Check that dst isn't written to beyond BlockSize even if there is room
137+
// in the slice
138+
dst = buff[endOfPrefix:] // Extend dst to include suffix
139+
cipher(dst, src)
140+
if !bytes.Equal(buff[startOfSuffix:], initSuffix) {
141+
t.Errorf("block cipher modified dst past BlockSize bytes; got %x, want %x", buff[startOfSuffix:], initSuffix)
142+
}
143+
})
144+
145+
// Check that output of cipher isn't affected by adjacent data beyond input
146+
// slice scope
147+
// For encryption, this assumes block ciphers encrypt deterministically
148+
t.Run("OutOfBoundsRead", func(t *testing.T) {
149+
rng := newRandReader(t)
150+
151+
src := make([]byte, blockSize)
152+
rng.Read(src)
153+
expectedDst := make([]byte, blockSize)
154+
cipher(expectedDst, src)
155+
156+
// Make a buffer with src in the middle and data on either end
157+
buff := make([]byte, blockSize*3)
158+
endOfPrefix, startOfSuffix := blockSize, blockSize*2
159+
160+
copy(buff[endOfPrefix:startOfSuffix], src)
161+
rng.Read(buff[:endOfPrefix])
162+
rng.Read(buff[startOfSuffix:])
163+
164+
testDst := make([]byte, blockSize)
165+
cipher(testDst, buff[endOfPrefix:startOfSuffix])
166+
if !bytes.Equal(testDst, expectedDst) {
167+
t.Errorf("block cipher affected by data outside of src slice bounds; got %x, want %x", testDst, expectedDst)
168+
}
169+
170+
// Check that src isn't read from beyond BlockSize even if the slice is
171+
// longer and contains data in the suffix
172+
cipher(testDst, buff[endOfPrefix:]) // Input long src
173+
if !bytes.Equal(testDst, expectedDst) {
174+
t.Errorf("block cipher affected by src data beyond BlockSize bytes; got %x, want %x", buff[startOfSuffix:], expectedDst)
175+
}
176+
})
177+
178+
t.Run("NonZeroDst", func(t *testing.T) {
179+
rng := newRandReader(t)
180+
181+
// Record what the cipher writes into a destination of zeroes
182+
src := make([]byte, blockSize)
183+
rng.Read(src)
184+
expectedDst := make([]byte, blockSize)
185+
186+
cipher(expectedDst, src)
187+
188+
// Make nonzero dst
189+
dst := make([]byte, blockSize*2)
190+
rng.Read(dst)
191+
192+
// Remember the random suffix which shouldn't be written to
193+
expectedDst = append(expectedDst, dst[blockSize:]...)
194+
195+
cipher(dst, src)
196+
if !bytes.Equal(dst, expectedDst) {
197+
t.Errorf("block cipher behavior differs when given non-zero dst; got %x, want %x", dst, expectedDst)
198+
}
199+
})
200+
201+
t.Run("BufferOverlap", func(t *testing.T) {
202+
rng := newRandReader(t)
203+
204+
buff := make([]byte, blockSize*2)
205+
rng.Read((buff))
206+
207+
// Make src and dst slices point to same array with inexact overlap
208+
src := buff[:blockSize]
209+
dst := buff[1 : blockSize+1]
210+
mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) })
211+
212+
// Only overlap on one byte
213+
src = buff[:blockSize]
214+
dst = buff[blockSize-1 : 2*blockSize-1]
215+
mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) })
216+
217+
// src comes after dst with one byte overlap
218+
src = buff[blockSize-1 : 2*blockSize-1]
219+
dst = buff[:blockSize]
220+
mustPanic(t, "invalid buffer overlap", func() { cipher(dst, src) })
221+
})
222+
223+
// Test short input/output.
224+
// Assembly used to not notice.
225+
// See issue 7928.
226+
t.Run("ShortBlock", func(t *testing.T) {
227+
// Returns slice of n bytes of an n+1 length array. Lets us test that a
228+
// slice is still considered too short even if the underlying array it
229+
// points to is large enough
230+
byteSlice := func(n int) []byte { return make([]byte, n+1)[0:n] }
231+
232+
// Off by one byte
233+
mustPanic(t, "input not full block", func() { cipher(byteSlice(blockSize), byteSlice(blockSize-1)) })
234+
mustPanic(t, "output not full block", func() { cipher(byteSlice(blockSize-1), byteSlice(blockSize)) })
235+
236+
// Small slices
237+
mustPanic(t, "input not full block", func() { cipher(byteSlice(1), byteSlice(1)) })
238+
mustPanic(t, "input not full block", func() { cipher(byteSlice(100), byteSlice(1)) })
239+
mustPanic(t, "output not full block", func() { cipher(byteSlice(1), byteSlice(100)) })
240+
})
241+
}
242+
243+
func mustPanic(t *testing.T, msg string, f func()) {
244+
t.Helper()
245+
246+
defer func() {
247+
t.Helper()
248+
249+
err := recover()
250+
251+
if err == nil {
252+
t.Errorf("function did not panic for %q", msg)
253+
}
254+
}()
255+
f()
256+
}

internal/cryptotest/hash.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2024 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package cryptotest
6+
7+
import (
8+
"io"
9+
"math/rand"
10+
"testing"
11+
"time"
12+
)
13+
14+
func newRandReader(t *testing.T) io.Reader {
15+
seed := time.Now().UnixNano()
16+
t.Logf("Deterministic RNG seed: 0x%x", seed)
17+
return rand.New(rand.NewSource(seed))
18+
}

0 commit comments

Comments
 (0)