From 41f4ec0e83f16fa308e61cb3011c3f4812734c87 Mon Sep 17 00:00:00 2001 From: Sun Yimin Date: Fri, 5 Jul 2024 16:07:38 +0800 Subject: [PATCH] pkcs8: support legacy PBES1 algorithms (parse only) --- pkcs/internal/md2/md2.go | 158 +++++++++++++++ pkcs/internal/md2/md2_test.go | 32 ++++ pkcs/internal/rc2/rc2.go | 308 ++++++++++++++++++++++++++++++ pkcs/internal/rc2/rc2_test.go | 96 ++++++++++ pkcs/pkcs5_pbes1.go | 99 ++++++++++ pkcs/{pbes2.go => pkcs5_pbes2.go} | 4 + pkcs8/pkcs8.go | 34 ++-- pkcs8/pkcs8_test.go | 67 ++++++- 8 files changed, 782 insertions(+), 16 deletions(-) create mode 100644 pkcs/internal/md2/md2.go create mode 100644 pkcs/internal/md2/md2_test.go create mode 100644 pkcs/internal/rc2/rc2.go create mode 100644 pkcs/internal/rc2/rc2_test.go create mode 100644 pkcs/pkcs5_pbes1.go rename pkcs/{pbes2.go => pkcs5_pbes2.go} (94%) diff --git a/pkcs/internal/md2/md2.go b/pkcs/internal/md2/md2.go new file mode 100644 index 0000000..3751e75 --- /dev/null +++ b/pkcs/internal/md2/md2.go @@ -0,0 +1,158 @@ +// Package md2 implements the MD2 hash algorithm as defined in RFC 1319. +// +// MD2 is cryptographically broken and should not be used for secure +// applications. +package md2 + +import "hash" + +// Size the size of a MD2 checksum in bytes. +const Size int = 16 + +// SizeBitSize the bit size of Size. +const SizeBitSize = 4 + +// BlockSize the blocksize of MD2 in bytes. +const BlockSize int = 16 + +var piSubst = [256]byte{ + 0x29, 0x2E, 0x43, 0xC9, 0xA2, 0xD8, 0x7C, 0x01, 0x3D, 0x36, 0x54, 0xA1, 0xEC, 0xF0, 0x06, 0x13, + 0x62, 0xA7, 0x05, 0xF3, 0xC0, 0xC7, 0x73, 0x8C, 0x98, 0x93, 0x2B, 0xD9, 0xBC, 0x4C, 0x82, 0xCA, + 0x1E, 0x9B, 0x57, 0x3C, 0xFD, 0xD4, 0xE0, 0x16, 0x67, 0x42, 0x6F, 0x18, 0x8A, 0x17, 0xE5, 0x12, + 0xBE, 0x4E, 0xC4, 0xD6, 0xDA, 0x9E, 0xDE, 0x49, 0xA0, 0xFB, 0xF5, 0x8E, 0xBB, 0x2F, 0xEE, 0x7A, + 0xA9, 0x68, 0x79, 0x91, 0x15, 0xB2, 0x07, 0x3F, 0x94, 0xC2, 0x10, 0x89, 0x0B, 0x22, 0x5F, 0x21, + 0x80, 0x7F, 0x5D, 0x9A, 0x5A, 0x90, 0x32, 0x27, 0x35, 0x3E, 0xCC, 0xE7, 0xBF, 0xF7, 0x97, 0x03, + 0xFF, 0x19, 0x30, 0xB3, 0x48, 0xA5, 0xB5, 0xD1, 0xD7, 0x5E, 0x92, 0x2A, 0xAC, 0x56, 0xAA, 0xC6, + 0x4F, 0xB8, 0x38, 0xD2, 0x96, 0xA4, 0x7D, 0xB6, 0x76, 0xFC, 0x6B, 0xE2, 0x9C, 0x74, 0x04, 0xF1, + 0x45, 0x9D, 0x70, 0x59, 0x64, 0x71, 0x87, 0x20, 0x86, 0x5B, 0xCF, 0x65, 0xE6, 0x2D, 0xA8, 0x02, + 0x1B, 0x60, 0x25, 0xAD, 0xAE, 0xB0, 0xB9, 0xF6, 0x1C, 0x46, 0x61, 0x69, 0x34, 0x40, 0x7E, 0x0F, + 0x55, 0x47, 0xA3, 0x23, 0xDD, 0x51, 0xAF, 0x3A, 0xC3, 0x5C, 0xF9, 0xCE, 0xBA, 0xC5, 0xEA, 0x26, + 0x2C, 0x53, 0x0D, 0x6E, 0x85, 0x28, 0x84, 0x09, 0xD3, 0xDF, 0xCD, 0xF4, 0x41, 0x81, 0x4D, 0x52, + 0x6A, 0xDC, 0x37, 0xC8, 0x6C, 0xC1, 0xAB, 0xFA, 0x24, 0xE1, 0x7B, 0x08, 0x0C, 0xBD, 0xB1, 0x4A, + 0x78, 0x88, 0x95, 0x8B, 0xE3, 0x63, 0xE8, 0x6D, 0xE9, 0xCB, 0xD5, 0xFE, 0x3B, 0x00, 0x1D, 0x39, + 0xF2, 0xEF, 0xB7, 0x0E, 0x66, 0x58, 0xD0, 0xE4, 0xA6, 0x77, 0x72, 0xF8, 0xEB, 0x75, 0x4B, 0x0A, + 0x31, 0x44, 0x50, 0xB4, 0x8F, 0xED, 0x1F, 0x1A, 0xDB, 0x99, 0x8D, 0x33, 0x9F, 0x11, 0x83, 0x14, +} + +// digest represents the partial evaluation of a checksum. +type digest struct { + s [Size]byte // state + c [BlockSize]byte // checksum + x [BlockSize]byte // buffer + nx int + len uint64 +} + +func (d *digest) Reset() { + for i := range d.s { + d.s[i] = 0 + } + for i := range d.c { + d.c[i] = 0 + } + d.nx = 0 + d.len = 0 +} + +// New returns a new hash.Hash computing the MD2 checksum. +func New() hash.Hash { + d := new(digest) + d.Reset() + return d +} + +func (d *digest) Size() int { return Size } + +func (d *digest) BlockSize() int { return BlockSize } + +func (d *digest) Write(p []byte) (nn int, err error) { + nn = len(p) + d.len += uint64(nn) + if d.nx > 0 { + n := copy(d.x[d.nx:], p) + d.nx += n + if d.nx == BlockSize { + block(d, d.x[:]) + d.nx = 0 + } + p = p[n:] + } + if len(p) >= BlockSize { + n := len(p) &^ (BlockSize - 1) + block(d, p[:n]) + p = p[n:] + } + if len(p) > 0 { + d.nx = copy(d.x[:], p) + } + return +} + +func (d *digest) Sum(in []byte) []byte { + // Make a copy of d so that caller can keep writing and summing. + d0 := *d + hash := d0.checkSum() + return append(in, hash[:]...) +} + +func (d *digest) checkSum() [Size]byte { + // Pad out to multiple of BlockSize bytes. + var tmp [BlockSize]byte + padLen := BlockSize - d.nx + for i := 0; i < padLen; i++ { + tmp[i] = byte(padLen) + } + d.Write(tmp[:padLen]) + // The previous write ensures that a whole number of + // blocks (i.e. a multiple of 16 bytes) have been hashed. + if d.nx != 0 { + panic("d.nx != 0") + } + + // Append the checksum + d.Write(d.c[:]) + + var digest [Size]byte + copy(digest[:], d.s[:]) + return digest +} + +func block(dig *digest, p []byte) { + var X [48]byte + for i := 0; i <= len(p)-BlockSize; i += BlockSize { + // Form encryption block from state, block, and state ^ block. + copy(X[:16], dig.s[:]) + copy(X[16:32], p[i:i+BlockSize]) + for j := 0; j < BlockSize; j++ { + X[32+j] = X[16+j] ^ X[j] + } + + // Encrypt block (18 rounds) + t := byte(0) + for j := 0; j < 18; j++ { + for k := 0; k < 48; k++ { + X[k] ^= piSubst[t] + t = X[k] + } + t = t + byte(j) + } + + // Save state + copy(dig.s[:], X[:16]) + + // Update checksum + t = dig.c[15] + for j := 0; j < 16; j++ { + dig.c[j] ^= piSubst[p[i+j]^t] + t = dig.c[j] + } + } +} + +// Sum returns the MD2 checksum of the data. +func Sum(data []byte) [Size]byte { + var d digest + d.Reset() + d.Write(data) + return d.checkSum() +} diff --git a/pkcs/internal/md2/md2_test.go b/pkcs/internal/md2/md2_test.go new file mode 100644 index 0000000..fd6b446 --- /dev/null +++ b/pkcs/internal/md2/md2_test.go @@ -0,0 +1,32 @@ +package md2 + +import ( + "fmt" + "testing" +) + +type md2Test struct { + out string + in string +} + +var golden = []md2Test{ + {"8350e5a3e24c153df2275c9f80692773", ""}, + {"32ec01ec4a6dac72c0ab96fb34c0b5d1", "a"}, + {"da853b0d3f88d99b30283a69e6ded6bb", "abc"}, + {"ab4f496bfb2a530b219ff33031fe06b0", "message digest"}, + {"4e8ddff3650292ab5a4108c3aa47940b", "abcdefghijklmnopqrstuvwxyz"}, + {"da33def2a42df13975352846c30338cd", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"}, + {"d5976f79d83d3a0dc9806c3c66f3efd8", "12345678901234567890123456789012345678901234567890123456789012345678901234567890"}, +} + +func TestGolden(t *testing.T) { + for i := 0; i < len(golden); i++ { + g := golden[i] + h := Sum([]byte(g.in)) + s := fmt.Sprintf("%x", h) + if s != g.out { + t.Fatalf("MD2 function: md2(%s) = %s want %s", g.in, s, g.out) + } + } +} diff --git a/pkcs/internal/rc2/rc2.go b/pkcs/internal/rc2/rc2.go new file mode 100644 index 0000000..02e9762 --- /dev/null +++ b/pkcs/internal/rc2/rc2.go @@ -0,0 +1,308 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package rc2 implements the RC2 cipher +/* +https://www.ietf.org/rfc/rfc2268.txt +http://people.csail.mit.edu/rivest/pubs/KRRR98.pdf + +This code is licensed under the MIT license. +*/ +package rc2 + +import ( + "crypto/cipher" + "encoding/binary" + "fmt" + + "github.com/emmansun/gmsm/internal/alias" +) + +// The rc2 block size in bytes +const BlockSize = 8 + +type rc2Cipher struct { + k [64]uint16 +} + +// NewCipherWithEffectiveKeyBits returns a new rc2 cipher with the given key and effective key length in bits t1 +func NewCipherWithEffectiveKeyBits(key []byte, t1 int) (cipher.Block, error) { + kLen := len(key) + if kLen < 1 || kLen > 128 { + return nil, fmt.Errorf("rc2: invalid key size %d", kLen) + } + if t1 < 1 || t1 > 1024 { + return nil, fmt.Errorf("rc2: invalid effective key length %d", t1) + } + return &rc2Cipher{ + k: expandKey(key, t1), + }, nil +} + +// NewCipher returns a new rc2 cipher with the given key +func NewCipher(key []byte) (cipher.Block, error) { + return NewCipherWithEffectiveKeyBits(key, len(key)*8) +} + +func (*rc2Cipher) BlockSize() int { return BlockSize } + +var piTable = [256]byte{ + 0xd9, 0x78, 0xf9, 0xc4, 0x19, 0xdd, 0xb5, 0xed, 0x28, 0xe9, 0xfd, 0x79, 0x4a, 0xa0, 0xd8, 0x9d, + 0xc6, 0x7e, 0x37, 0x83, 0x2b, 0x76, 0x53, 0x8e, 0x62, 0x4c, 0x64, 0x88, 0x44, 0x8b, 0xfb, 0xa2, + 0x17, 0x9a, 0x59, 0xf5, 0x87, 0xb3, 0x4f, 0x13, 0x61, 0x45, 0x6d, 0x8d, 0x09, 0x81, 0x7d, 0x32, + 0xbd, 0x8f, 0x40, 0xeb, 0x86, 0xb7, 0x7b, 0x0b, 0xf0, 0x95, 0x21, 0x22, 0x5c, 0x6b, 0x4e, 0x82, + 0x54, 0xd6, 0x65, 0x93, 0xce, 0x60, 0xb2, 0x1c, 0x73, 0x56, 0xc0, 0x14, 0xa7, 0x8c, 0xf1, 0xdc, + 0x12, 0x75, 0xca, 0x1f, 0x3b, 0xbe, 0xe4, 0xd1, 0x42, 0x3d, 0xd4, 0x30, 0xa3, 0x3c, 0xb6, 0x26, + 0x6f, 0xbf, 0x0e, 0xda, 0x46, 0x69, 0x07, 0x57, 0x27, 0xf2, 0x1d, 0x9b, 0xbc, 0x94, 0x43, 0x03, + 0xf8, 0x11, 0xc7, 0xf6, 0x90, 0xef, 0x3e, 0xe7, 0x06, 0xc3, 0xd5, 0x2f, 0xc8, 0x66, 0x1e, 0xd7, + 0x08, 0xe8, 0xea, 0xde, 0x80, 0x52, 0xee, 0xf7, 0x84, 0xaa, 0x72, 0xac, 0x35, 0x4d, 0x6a, 0x2a, + 0x96, 0x1a, 0xd2, 0x71, 0x5a, 0x15, 0x49, 0x74, 0x4b, 0x9f, 0xd0, 0x5e, 0x04, 0x18, 0xa4, 0xec, + 0xc2, 0xe0, 0x41, 0x6e, 0x0f, 0x51, 0xcb, 0xcc, 0x24, 0x91, 0xaf, 0x50, 0xa1, 0xf4, 0x70, 0x39, + 0x99, 0x7c, 0x3a, 0x85, 0x23, 0xb8, 0xb4, 0x7a, 0xfc, 0x02, 0x36, 0x5b, 0x25, 0x55, 0x97, 0x31, + 0x2d, 0x5d, 0xfa, 0x98, 0xe3, 0x8a, 0x92, 0xae, 0x05, 0xdf, 0x29, 0x10, 0x67, 0x6c, 0xba, 0xc9, + 0xd3, 0x00, 0xe6, 0xcf, 0xe1, 0x9e, 0xa8, 0x2c, 0x63, 0x16, 0x01, 0x3f, 0x58, 0xe2, 0x89, 0xa9, + 0x0d, 0x38, 0x34, 0x1b, 0xab, 0x33, 0xff, 0xb0, 0xbb, 0x48, 0x0c, 0x5f, 0xb9, 0xb1, 0xcd, 0x2e, + 0xc5, 0xf3, 0xdb, 0x47, 0xe5, 0xa5, 0x9c, 0x77, 0x0a, 0xa6, 0x20, 0x68, 0xfe, 0x7f, 0xc1, 0xad, +} + +func expandKey(key []byte, t1 int) [64]uint16 { + l := make([]byte, 128) + copy(l, key) + + var t = len(key) + var t8 = (t1 + 7) / 8 // effective key length in bytes + var tm = byte(255 % uint(1<<(8+uint(t1)-8*uint(t8)))) // mask for the t1 rightmost bits of the last byte + + for i := t; i < 128; i++ { + l[i] = piTable[l[i-1]+l[i-t]] + } + + l[128-t8] = piTable[l[128-t8]&tm] + + for i := 127 - t8; i >= 0; i-- { + l[i] = piTable[l[i+1]^l[i+t8]] + } + + var k [64]uint16 + + for i := range k { + k[i] = uint16(l[2*i]) | uint16(l[2*i+1])<<8 + } + + return k +} + +// rotl16 rotates x left by b bits +func rotl16(x uint16, b uint) uint16 { + return (x >> (16 - b)) | (x << b) +} + +func (c *rc2Cipher) Encrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("rc2: input not full block") + } + if len(dst) < BlockSize { + panic("rc2: output not full block") + } + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("rc2: invalid buffer overlap") + } + + r0 := binary.LittleEndian.Uint16(src[0:2]) + r1 := binary.LittleEndian.Uint16(src[2:4]) + r2 := binary.LittleEndian.Uint16(src[4:6]) + r3 := binary.LittleEndian.Uint16(src[6:BlockSize]) + + var j int + + // perform 5 mixing rounds + for j <= 16 { + // mix up r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix up r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix up r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix up r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + } + + // perform 1 mashing round + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + // perform 6 mixing rounds + for j <= 40 { + // mix up r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix up r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix up r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix up r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + } + + // perform 1 mashing round + r0 = r0 + c.k[r3&63] + r1 = r1 + c.k[r0&63] + r2 = r2 + c.k[r1&63] + r3 = r3 + c.k[r2&63] + + // perform 5 mixing rounds + for j <= 60 { + // mix up r0 + r0 = r0 + c.k[j] + (r3 & r2) + ((^r3) & r1) + r0 = rotl16(r0, 1) + j++ + + // mix up r1 + r1 = r1 + c.k[j] + (r0 & r3) + ((^r0) & r2) + r1 = rotl16(r1, 2) + j++ + + // mix up r2 + r2 = r2 + c.k[j] + (r1 & r0) + ((^r1) & r3) + r2 = rotl16(r2, 3) + j++ + + // mix up r3 + r3 = r3 + c.k[j] + (r2 & r1) + ((^r2) & r0) + r3 = rotl16(r3, 5) + j++ + } + + binary.LittleEndian.PutUint16(dst[0:2], r0) + binary.LittleEndian.PutUint16(dst[2:4], r1) + binary.LittleEndian.PutUint16(dst[4:6], r2) + binary.LittleEndian.PutUint16(dst[6:BlockSize], r3) +} + +func (c *rc2Cipher) Decrypt(dst, src []byte) { + if len(src) < BlockSize { + panic("rc2: input not full block") + } + if len(dst) < BlockSize { + panic("rc2: output not full block") + } + if alias.InexactOverlap(dst[:BlockSize], src[:BlockSize]) { + panic("rc2: invalid buffer overlap") + } + r0 := binary.LittleEndian.Uint16(src[0:2]) + r1 := binary.LittleEndian.Uint16(src[2:4]) + r2 := binary.LittleEndian.Uint16(src[4:6]) + r3 := binary.LittleEndian.Uint16(src[6:BlockSize]) + + j := 63 + + // perform 5 r-mixing rounds + for j >= 44 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + } + + // perform 1 r-mashing round + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + // perform 6 r-mixing rounds + for j >= 20 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + } + + // perform 1 r-mashing round + r3 = r3 - c.k[r2&63] + r2 = r2 - c.k[r1&63] + r1 = r1 - c.k[r0&63] + r0 = r0 - c.k[r3&63] + + // perform 5 r-mixing rounds + for j >= 3 { + // unmix r3 + r3 = rotl16(r3, 16-5) + r3 = r3 - c.k[j] - (r2 & r1) - ((^r2) & r0) + j-- + + // unmix r2 + r2 = rotl16(r2, 16-3) + r2 = r2 - c.k[j] - (r1 & r0) - ((^r1) & r3) + j-- + + // unmix r1 + r1 = rotl16(r1, 16-2) + r1 = r1 - c.k[j] - (r0 & r3) - ((^r0) & r2) + j-- + + // unmix r0 + r0 = rotl16(r0, 16-1) + r0 = r0 - c.k[j] - (r3 & r2) - ((^r3) & r1) + j-- + } + + binary.LittleEndian.PutUint16(dst[0:2], r0) + binary.LittleEndian.PutUint16(dst[2:4], r1) + binary.LittleEndian.PutUint16(dst[4:6], r2) + binary.LittleEndian.PutUint16(dst[6:BlockSize], r3) +} diff --git a/pkcs/internal/rc2/rc2_test.go b/pkcs/internal/rc2/rc2_test.go new file mode 100644 index 0000000..ba3a217 --- /dev/null +++ b/pkcs/internal/rc2/rc2_test.go @@ -0,0 +1,96 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package rc2 + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestEncryptDecrypt(t *testing.T) { + // TODO(dgryski): add the rest of the test vectors from the RFC + var tests = []struct { + key string + plain string + cipher string + t1 int + }{ + { + "0000000000000000", + "0000000000000000", + "ebb773f993278eff", + 63, + }, + { + "ffffffffffffffff", + "ffffffffffffffff", + "278b27e42e2f0d49", + 64, + }, + { + "3000000000000000", + "1000000000000001", + "30649edf9be7d2c2", + 64, + }, + { + "88", + "0000000000000000", + "61a8a244adacccf0", + 64, + }, + { + "88bca90e90875a", + "0000000000000000", + "6ccf4308974c267f", + 64, + }, + { + "88bca90e90875a7f0f79c384627bafb2", + "0000000000000000", + "1a807d272bbe5db1", + 64, + }, + { + "88bca90e90875a7f0f79c384627bafb2", + "0000000000000000", + "2269552ab0f85ca6", + 128, + }, + { + "88bca90e90875a7f0f79c384627bafb216f80a6f85920584c42fceb0be255daf1e", + "0000000000000000", + "5b78d3a43dfff1f1", + 129, + }, + } + + for _, tt := range tests { + k, _ := hex.DecodeString(tt.key) + p, _ := hex.DecodeString(tt.plain) + c, _ := hex.DecodeString(tt.cipher) + + b, err := NewCipherWithEffectiveKeyBits(k, tt.t1) + if err != nil { + t.Errorf("New(%q, %d) failed: %v", tt.key, tt.t1, err) + continue + } + + var dst [8]byte + + b.Encrypt(dst[:], p) + + if !bytes.Equal(dst[:], c) { + t.Errorf("encrypt failed: got % 2x wanted % 2x\n", dst, c) + } + + b.Decrypt(dst[:], c) + + if !bytes.Equal(dst[:], p) { + t.Errorf("decrypt failed: got % 2x wanted % 2x\n", dst, p) + } + } +} diff --git a/pkcs/pkcs5_pbes1.go b/pkcs/pkcs5_pbes1.go new file mode 100644 index 0000000..65cd8d0 --- /dev/null +++ b/pkcs/pkcs5_pbes1.go @@ -0,0 +1,99 @@ +package pkcs + +import ( + "crypto/cipher" + "crypto/des" + "crypto/md5" + "crypto/sha1" + "crypto/x509/pkix" + "encoding/asn1" + "errors" + "hash" + + "github.com/emmansun/gmsm/pkcs/internal/md2" + "github.com/emmansun/gmsm/pkcs/internal/rc2" +) + +var ( + pbeWithMD2AndDESCBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 1} + pbeWithMD2AndRC2CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 4} + pbeWithMD5AndDESCBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 3} + pbeWithMD5AndRC2CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 6} + pbeWithSHA1AndDESCBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 10} + pbeWithSHA1AndRC2CBC = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 11} +) + +type pbeParameter struct { + Salt []byte + Iteration int +} + +// PBES1 implements the Password-Based Encryption Scheme 1. +type PBES1 struct { + Algorithm pkix.AlgorithmIdentifier +} + +// Key returns the key derived from the password according PBKDF1. +func (pbes1 *PBES1) Key(password []byte) ([]byte, error) { + param := new(pbeParameter) + if _, err := asn1.Unmarshal(pbes1.Algorithm.Parameters.FullBytes, param); err != nil { + return nil, err + } + var hash hash.Hash + switch { + case pbes1.Algorithm.Algorithm.Equal(pbeWithMD2AndDESCBC) || pbes1.Algorithm.Algorithm.Equal(pbeWithMD2AndRC2CBC): + hash = md2.New() + case pbes1.Algorithm.Algorithm.Equal(pbeWithMD5AndDESCBC) || pbes1.Algorithm.Algorithm.Equal(pbeWithMD5AndRC2CBC): + hash = md5.New() + case pbes1.Algorithm.Algorithm.Equal(pbeWithSHA1AndDESCBC) || pbes1.Algorithm.Algorithm.Equal(pbeWithSHA1AndRC2CBC): + hash = sha1.New() + default: + return nil, errors.New("pkcs5: unsupported pbes1 cipher") + } + hash.Write(password) + hash.Write(param.Salt) + key := hash.Sum(nil) + for i := 1; i < param.Iteration; i++ { + hash.Reset() + hash.Write(key) + key = hash.Sum(key[:0]) + } + return key, nil +} + +func (pbes1 *PBES1) Decrypt(password, ciphertext []byte) ([]byte, KDFParameters, error) { + key, err := pbes1.Key(password) + if err != nil { + return nil, nil, err + } + var block cipher.Block + switch { + case pbes1.Algorithm.Algorithm.Equal(pbeWithMD2AndDESCBC) || + pbes1.Algorithm.Algorithm.Equal(pbeWithMD5AndDESCBC) || + pbes1.Algorithm.Algorithm.Equal(pbeWithSHA1AndDESCBC): + block, err = des.NewCipher(key[:8]) + case pbes1.Algorithm.Algorithm.Equal(pbeWithMD2AndRC2CBC) || + pbes1.Algorithm.Algorithm.Equal(pbeWithMD5AndRC2CBC) || + pbes1.Algorithm.Algorithm.Equal(pbeWithSHA1AndRC2CBC): + block, err = rc2.NewCipher(key[:8]) + default: + return nil, nil, errors.New("pkcs5: unsupported pbes1 cipher") + } + if err != nil { + return nil, nil, err + } + plaintext, err := cbcDecrypt(block, key[8:16], ciphertext) + if err != nil { + return nil, nil, err + } + return plaintext, nil, nil +} + +func IsPBES1(algorithm pkix.AlgorithmIdentifier) bool { + return algorithm.Algorithm.Equal(pbeWithMD2AndDESCBC) || + algorithm.Algorithm.Equal(pbeWithMD2AndRC2CBC) || + algorithm.Algorithm.Equal(pbeWithMD5AndDESCBC) || + algorithm.Algorithm.Equal(pbeWithMD5AndRC2CBC) || + algorithm.Algorithm.Equal(pbeWithSHA1AndDESCBC) || + algorithm.Algorithm.Equal(pbeWithSHA1AndRC2CBC) +} diff --git a/pkcs/pbes2.go b/pkcs/pkcs5_pbes2.go similarity index 94% rename from pkcs/pbes2.go rename to pkcs/pkcs5_pbes2.go index 22b087e..efdd612 100644 --- a/pkcs/pbes2.go +++ b/pkcs/pkcs5_pbes2.go @@ -94,6 +94,10 @@ type KDFOpts interface { OID() asn1.ObjectIdentifier } +type PBESEncrypter interface { + Encrypt(rand io.Reader, password, plaintext []byte) (*pkix.AlgorithmIdentifier, []byte, error) +} + // KDFParameters contains parameters (salt, etc.) for a key deriviation function. // It must be a ASN.1-decodable structure. // An implementation of this interface is created when decoding an encrypted PKCS#8 key. diff --git a/pkcs8/pkcs8.go b/pkcs8/pkcs8.go index 089eda8..c899358 100644 --- a/pkcs8/pkcs8.go +++ b/pkcs8/pkcs8.go @@ -20,6 +20,7 @@ type Opts = pkcs.PBES2Opts type PBKDF2Opts = pkcs.PBKDF2Opts type ScryptOpts = pkcs.ScryptOpts +var DefaultOpts = pkcs.DefaultOpts var SM3 = pkcs.SM3 var SHA1 = pkcs.SHA1 var SHA224 = pkcs.SHA224 @@ -54,20 +55,25 @@ func ParsePrivateKey(der []byte, password []byte) (any, pkcs.KDFParameters, erro return nil, nil, errors.New("pkcs8: only PKCS #5 v2.0 supported") } - if !pkcs.IsPBES2(privKey.EncryptionAlgorithm) { - return nil, nil, errors.New("pkcs8: only PBES2 supported") + var kdfParams pkcs.KDFParameters + var decryptedKey []byte + var err error + switch { + case pkcs.IsPBES2(privKey.EncryptionAlgorithm): + var params pkcs.PBES2Params + if _, err := asn1.Unmarshal(privKey.EncryptionAlgorithm.Parameters.FullBytes, ¶ms); err != nil { + return nil, nil, errors.New("pkcs8: invalid PBES2 parameters") + } + decryptedKey, kdfParams, err = params.Decrypt(password, privKey.EncryptedData) + case pkcs.IsPBES1(privKey.EncryptionAlgorithm): + pbes1 := &pkcs.PBES1{Algorithm: privKey.EncryptionAlgorithm} + decryptedKey, kdfParams, err = pbes1.Decrypt(password, privKey.EncryptedData) + default: + return nil, nil, errors.New("pkcs8: only part of PBES1/PBES2 supported") } - - var params pkcs.PBES2Params - if _, err := asn1.Unmarshal(privKey.EncryptionAlgorithm.Parameters.FullBytes, ¶ms); err != nil { - return nil, nil, errors.New("pkcs8: invalid PBES2 parameters") - } - - decryptedKey, kdfParams, err := params.Decrypt(password, privKey.EncryptedData) if err != nil { return nil, nil, err } - key, err := smx509.ParsePKCS8PrivateKey(decryptedKey) if err != nil { return nil, nil, errors.New("pkcs8: incorrect password? failed to parse private key while ParsePKCS8PrivateKey: " + err.Error()) @@ -77,13 +83,13 @@ func ParsePrivateKey(der []byte, password []byte) (any, pkcs.KDFParameters, erro // MarshalPrivateKey encodes a private key into DER-encoded PKCS#8 with the given options. // Password can be nil. -func MarshalPrivateKey(priv any, password []byte, opts *Opts) ([]byte, error) { +func MarshalPrivateKey(priv any, password []byte, encrypter pkcs.PBESEncrypter) ([]byte, error) { if len(password) == 0 { return smx509.MarshalPKCS8PrivateKey(priv) } - if opts == nil { - opts = pkcs.DefaultOpts + if encrypter == nil { + encrypter = DefaultOpts } // Convert private key into PKCS8 format @@ -92,7 +98,7 @@ func MarshalPrivateKey(priv any, password []byte, opts *Opts) ([]byte, error) { return nil, err } - encryptionAlgorithm, encryptedKey, err := opts.Encrypt(rand.Reader, password, pkey) + encryptionAlgorithm, encryptedKey, err := encrypter.Encrypt(rand.Reader, password, pkey) if err != nil { return nil, err } diff --git a/pkcs8/pkcs8_test.go b/pkcs8/pkcs8_test.go index 0a72905..09ba510 100644 --- a/pkcs8/pkcs8_test.go +++ b/pkcs8/pkcs8_test.go @@ -181,6 +181,38 @@ zOuhMC9Oo3oMYlbEXAT9mq33MkGKMUth2ek/bQIvnCHG -----END ENCRYPTED PRIVATE KEY----- ` +const encryptedPBEWithMD5AndDES = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGwMBsGCSqGSIb3DQEFAzAOBAhsKeK+cnfdjAICCAAEgZAE5GZMjPQCLLifGK0r +ytlpt23Qas1KI6x7qmIP6oeYflCWT0Iv7AqK2cT8YK7s5Yy3j21YiHEG5FCr8Qb+ +GMlgQsRGkeU5y0I9zLZrhH9qOVJEuDLckCjMKbFXUEwx5YeBhQKTosB/quA5v9Lp +6SSLtKShYgx/MDJDarcAuj0whmNyTXijDGAMImltuqwsIUg= +-----END ENCRYPTED PRIVATE KEY----- +` + +const encyptedPBEWithSha1AndDES = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGwMBsGCSqGSIb3DQEFCjAOBAiFq+R6absk/wICCAAEgZB7T7BEaGkyMqw8xN9e +ldSPFAAXzcsPx83w1jvD8TvM7uwqUu9k0+2FnSMcuhOHjX03AFZ2JJXZZBWxZJ24 +GEWLwQYJIJ16el9n2DVPkp1qqbsXPMCyHR9hW4Qxt/9aXZmTLdqpAhQ9BfTSmpQp +Yt+/s6eXMOHP2C0sp5aOxnIjkorzfgasO/Y8JtMukVlKzqU= +-----END ENCRYPTED PRIVATE KEY----- +` + +const encryptedPBEWithSha1AndRC2_64 = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGwMBsGCSqGSIb3DQEFCzAOBAhNHGnZio3lKwICCAAEgZCGUGQ3b6zS/iBlJ6BY +mpmuPBmGnuwtOtTFshWaZL8kPUROkZVBrKt6a/oM0vsTbyEDeii9ktt2cnd3plwh +fqnOmJmOBwHVeltjRLYYFzs2JgX4bXSc9eg+/AugvsDPj+dgk0yMsRLjKoZw/w/U +qZTZq/iFYg4Q80Ew7gJUBaMdA6Af8K/YDc3If7y78L0AD74= +-----END ENCRYPTED PRIVATE KEY----- +` + +const encryptedPBEWithMD5AndRC2_40 = `-----BEGIN ENCRYPTED PRIVATE KEY----- +MIGwMBsGCSqGSIb3DQEFBjAOBAiZRjrXPXpYwgICCAAEgZBilgJ9rYtNt7Ih59zF +jnErhxny2wRPoK1Ng/bLNijlBnppryizyAHuujNmpDRf77pYFmszaaprWZDs2bQw +tlhqw20XVOBG2PhHXsL9LXfbm7lJOVpMtBYtbduascC1aA5Dref9L3nBNlP5zdMp +TFgdUgkic4/tuw6b5E3Ysn3ugAlPTAMm7b8Nd0Hs0P/81nA= +-----END ENCRYPTED PRIVATE KEY----- +` + func TestParsePKCS8PrivateKeyRSA(t *testing.T) { keyList := []struct { name string @@ -711,8 +743,8 @@ func TestParseInvalidPrivateKey(t *testing.T) { t.Fatal(err) } _, err = pkcs8.ParsePKCS8PrivateKeyECDSA(data, []byte("password")) - if err == nil || err.Error() != "pkcs8: only PBES2 supported" { - t.Errorf("should be error: pkcs8: only PBES2 supported") + if err == nil || err.Error() != "pkcs8: only part of PBES1/PBES2 supported" { + t.Errorf("should be error: only part of PBES1/PBES2 supported") } privKey.EncryptionAlgorithm.Algorithm = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 5, 13} @@ -725,3 +757,34 @@ func TestParseInvalidPrivateKey(t *testing.T) { t.Errorf("should be error: pkcs8: invalid PBES2 parameters") } } + +func TestParseLegacyPBES1PrivateKey(t *testing.T) { + block, _ := pem.Decode([]byte(encryptedPBEWithMD5AndDES)) + _, err := pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte("12345678")) + if err != nil { + t.Errorf("ParsePKCS8PrivateKey returned: %s", err) + } + + block, _ = pem.Decode([]byte(encyptedPBEWithSha1AndDES)) + _, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte("12345678")) + if err != nil { + t.Errorf("ParsePKCS8PrivateKey returned: %s", err) + } + + block, _ = pem.Decode([]byte(encryptedPBEWithSha1AndRC2_64)) + _, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte("12345678")) + if err != nil { + t.Errorf("ParsePKCS8PrivateKey returned: %s", err) + } + + block, _ = pem.Decode([]byte(encryptedPBEWithMD5AndRC2_40)) + _, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte("12345678")) + if err != nil { + t.Errorf("ParsePKCS8PrivateKey returned: %s", err) + } + + _, err = pkcs8.ParsePKCS8PrivateKey(block.Bytes, []byte("wrong pwd")) + if err == nil { + t.Errorf("should have failed") + } +}