youmark / pkcs8 Goto Github PK
View Code? Open in Web Editor NEWGo package implementing functions to parse and convert private keys in PKCS#8 format, as defined in RFC5208 and RFC5958
License: MIT License
Go package implementing functions to parse and convert private keys in PKCS#8 format, as defined in RFC5208 and RFC5958
License: MIT License
Hi,
can we get a bump of golang.org/x/crypto to the latest version 0.22
?
We currently face these security vulnerbilities in our security scans.
Couldn't able to parse password encrypted aes-256-cfb pkcs8 private keys. The library throws the below error,
pkcs8: unsupported cipher (OID: 2.16.840.1.101.3.4.1.44)
Can you please add the support for this?
var oidAES256CFB = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 1, 44}
var AES256CFB = cipherWithBlock{
ivSize: aes.BlockSize,
keySize: 32,
newBlock: aes.NewCipher,
oid: oidAES256CFB,
}
func init() {
RegisterCipher(oidAES256CFB, func() Cipher {
return AES256CFB
})
}
type cipherWithBlock struct {
oid asn1.ObjectIdentifier
ivSize int
keySize int
newBlock func(key []byte) (cipher.Block, error)
}
func (c cipherWithBlock) IVSize() int {
return c.ivSize
}
func (c cipherWithBlock) KeySize() int {
return c.keySize
}
func (c cipherWithBlock) OID() asn1.ObjectIdentifier {
return c.oid
}
func (c cipherWithBlock) Encrypt(key, iv, plaintext []byte) ([]byte, error) {
block, err := c.newBlock(key)
if err != nil {
return nil, err
}
return cfbEncrypt(block, key, iv, plaintext)
}
func (c cipherWithBlock) Decrypt(key, iv, ciphertext []byte) ([]byte, error) {
block, err := c.newBlock(key)
if err != nil {
return nil, err
}
return cfbDecrypt(block, key, iv, ciphertext)
}
func cfbEncrypt(block cipher.Block, key, iv, plaintext []byte) ([]byte, error) {
ciphertext := make([]byte, aes.BlockSize+len(plaintext))
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)
return ciphertext, nil
}
func cfbDecrypt(block cipher.Block, key, iv, ciphertext []byte) ([]byte, error) {
stream := cipher.NewCFBDecrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
stream.XORKeyStream(plaintext, ciphertext)
return plaintext, nil
}
Following #13 and some other pull requests it would be interesting to create a new release (1.1) so that we could easily import it with glide/dep.
Hello, I'm having some trouble where the decrypted RSA key from openssl differs from the one decrypted by this library. Here is a test running an openssl command to generate an encrypted key, running the decryption using this library and running the decryption using openssl, showing the two results differences. Am I doing something wrong or is the library at fault here? ๐ค
package main
import (
"crypto/x509"
"encoding/pem"
"os"
"os/exec"
"path/filepath"
"testing"
"github.com/youmark/pkcs8"
)
func TestXxx(t *testing.T) {
const password = "password"
directory := t.TempDir()
encryptedFilepath := filepath.Join(directory, "encrypted.pem")
decryptedFilepath := filepath.Join(directory, "decrypted.pem")
// Generate RSA AES-128-CBC encrypted key using the password
err := exec.Command("openssl", "genpkey", "-algorithm", "RSA",
"-aes-128-cbc", "-pass", "pass:"+password, "-out", encryptedFilepath).Run()
failOnError(t, err)
// Read and decrypt encrypted key with pkcs8 library
encryptedPEMBytes, err := os.ReadFile(encryptedFilepath)
failOnError(t, err)
encryptedPEMBlock, _ := pem.Decode(encryptedPEMBytes)
decryptedPrivateKey, err := pkcs8.ParsePKCS8PrivateKey(encryptedPEMBlock.Bytes, []byte(password))
failOnError(t, err)
// Encode decrypted private key to PKCS8
decryptedDER, err := x509.MarshalPKCS8PrivateKey(decryptedPrivateKey)
failOnError(t, err)
decryptedPEMBlock := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: decryptedDER,
}
decryptedPEMBytes := pem.EncodeToMemory(decryptedPEMBlock)
// Decrypt RSA AES-128-CBC encrypted key with openssl
err = exec.Command("openssl", "rsa", "-in", encryptedFilepath,
"-passin", "pass:"+password, "-out", decryptedFilepath).Run()
failOnError(t, err)
// Compare the two results
expectedDecryptedPEM, err := os.ReadFile(decryptedFilepath)
failOnError(t, err)
if string(expectedDecryptedPEM) != string(decryptedPEMBytes) {
t.Fatalf("expected: %s\nactual: %s", string(expectedDecryptedPEM), string(decryptedPEMBytes))
}
}
func failOnError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatal(err)
}
}
Any application that depends on this library will indirectly require golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
Currently mongo uses this library in its Go driver. Which my library uses. Resulting in security warnings.
Bumping golang.org/x/crypto
to at least version 0.21.0
will patch:
I was checking rclone
's dependencies and when updating pkcs8
I see the following
$ go get -u github.com/youmark/pkcs8@latest
go: downloading github.com/youmark/pkcs8 v0.0.0-20240424034433-3c2c7870ae76
(...)
go: upgraded github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a => v0.0.0-20240424034433-3c2c7870ae76
It's not pointing to the 1.2 version as go requires using "semver" release numbers X.Y.Z.
https://go.dev/blog/publishing-go-modules
Would it be possible in the future, or for current release, to tag releases with MAJOR.MINOR.PATCH format?
All are CBC mode currently . emmansun@03589b6
In this commit
The go version was updated to go1.22 in the go.mod here
Line 3 in 3c2c787
This means this package no longer compiles with pre go1.22
I don't know if that was your intention? I suspect not, so I'd suggest changing it go go1.20 or lower to allow a few versions grace.
Hi there,
I need the DES-CBC block cipher, it's relatively easy to do with a new file cipher_des.go
:
var (
oidDESCBC = asn1.ObjectIdentifier{1, 3, 14, 3, 2, 7}
)
func init() {
RegisterCipher(oidDESCBC, func() Cipher {
return DESCBC
})
}
// DESCBC is the DES cipher in CBC mode.
var DESCBC = cipherWithBlock{
ivSize: des.BlockSize,
keySize: 8,
newBlock: des.NewCipher,
oid: oidDESCBC,
}
Let me know if you want me to PR this or if you would see any problem with this. Thanks!
Consider the following snippet:
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/youmark/pkcs8"
)
func generate(bits int, passphrase string) ([]byte, error) {
key, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, err
}
block := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(key),
}
if passphrase != "" {
block, err = x509.EncryptPEMBlock(rand.Reader, block.Type, block.Bytes, []byte(passphrase), x509.PEMCipherAES256)
if err != nil {
return nil, err
}
}
return pem.EncodeToMemory(block), nil
}
func convert(pemBytes []byte, passphrase string) ([]byte, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, fmt.Errorf("no valid private key found")
}
var (
privateKeyBytes []byte
privateKey interface{}
err error
)
if x509.IsEncryptedPEMBlock(block) {
privateKeyBytes, err = x509.DecryptPEMBlock(block, []byte(passphrase))
if err != nil {
return nil, fmt.Errorf("couldn't decrypt private key")
}
} else {
privateKeyBytes = block.Bytes
}
switch block.Type {
case "RSA PRIVATE KEY":
privateKey, err = x509.ParsePKCS1PrivateKey(privateKeyBytes)
if err != nil {
return nil, fmt.Errorf("couldn't parse der encoded key: %v", privateKeyBytes)
}
default:
return nil, fmt.Errorf("unsupported key type %q", block.Type)
}
return pkcs8.ConvertPrivateKeyToPKCS8(privateKey, []byte(passphrase))
}
func main() {
passphrase := "unicorn"
pemBytes, err := generate(1024, passphrase)
if err != nil {
panic(err)
}
_, err = convert(pemBytes, passphrase)
if err != nil {
panic(err)
}
}
I'm trying to generate an encrypted RSA based private key and then converting it to PKCS#8. This results into a runtime error:
panic: runtime error: slice bounds out of range
goroutine 1 [running]:
github.com/youmark/pkcs8.convertPrivateKeyToPKCS8Encrypted(0x521b80, 0xc4201583c0, 0xc420010b58, 0x7, 0x8, 0x8, 0x43d483, 0xc42004be90, 0x53a0cf, 0x7)
/home/umayr/Code/go/src/github.com/youmark/pkcs8/pkcs8.go:210 +0x6d5
github.com/youmark/pkcs8.ConvertPrivateKeyToPKCS8(0x521b80, 0xc4201583c0, 0xc42004bee8, 0x1, 0x1, 0x20, 0x270, 0x0, 0x0, 0xc42014e8d0)
/home/umayr/Code/go/src/github.com/youmark/pkcs8/pkcs8.go:243 +0xc8
main.convert(0xc420164000, 0x3da, 0x778, 0x53a0cf, 0x7, 0x778, 0x0, 0x0, 0xc420000340, 0xc420000340)
/home/umayr/Code/go/src/github.com/umayr/pkcs8/ref.go:65 +0x3d6
main.main()
/home/umayr/Code/go/src/github.com/umayr/pkcs8/ref.go:75 +0xa4
exit status 2
With a little debugging, I think this line is causing the issue in pkcs8.go
:
pkey = pkey[0 : n+padding]
Here n
is the length of pkey
and above line is trying to create a slice from 0
to length of pkey + padding
which should return an error as demonstrated in this example.
Replacing this line with:
diff --git a/pkcs8.go b/pkcs8.go
index 0b6c6e0..dde6818 100644
--- a/pkcs8.go
+++ b/pkcs8.go
@@ -207,7 +207,7 @@ func convertPrivateKeyToPKCS8Encrypted(priv interface{}, password []byte) ([]byt
padding := aes.BlockSize - len(pkey)%aes.BlockSize
if padding > 0 {
n := len(pkey)
- pkey = pkey[0 : n+padding]
+ pkey = append(pkey, make([]byte, padding)...)
for i := 0; i < padding; i++ {
pkey[n+i] = byte(padding)
}
fixes this issue temporarily. I can have a PR ready for this with more efficient fix.
Private key format :
var encryptedKey = []byte(-----BEGIN ENCRYPTED PRIVATE KEY----- s+2e0UnZEGxw0JRV8+D4lY3Em+2KuaUkjxWduHSpLFT55FdRSLW1BL2zsbqdWgSO wvkbvTLkJZ6fouCOjfdIhRYuCg== -----END ENCRYPTED PRIVATE KEY-----
)
openssl asn1parse -in key.pem -i -dlimit 16
0:d=0 hl=4 l=2463 cons: SEQUENCE
4:d=1 hl=2 l= 73 cons: SEQUENCE
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2
17:d=2 hl=2 l= 60 cons: SEQUENCE
19:d=3 hl=2 l= 27 cons: SEQUENCE
21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2
32:d=4 hl=2 l= 14 cons: SEQUENCE
34:d=5 hl=2 l= 8 prim: OCTET STRING
0000 - d5 82 19 ef 1a d5 51 3e- ......Q>
44:d=5 hl=2 l= 2 prim: INTEGER :0800
48:d=3 hl=2 l= 29 cons: SEQUENCE
50:d=4 hl=2 l= 9 prim: OBJECT :aes-256-cbc
61:d=4 hl=2 l= 16 prim: OCTET STRING
0000 - 69 d8 7f 79 2a f8 62 58-f9 52 29 03 32 39 95 ac i..y*.bX.R).29..
79:d=1 hl=4 l=2384 prim: OCTET STRING
0000 - 44 17 d7 a2 8b 73 9b 51-6b a3 79 f6 53 d5 28 6d D....s.Qk.y.S.(m
I create a private key by openssl pkcs8 like this:
openssl pkcs8 -in rsa.key -out pkcs8.key -topk8 -passout pass:xxxxx -v2 aes-256-cbc -iter 4096
when i call the function ParsePKCS8PrivateKey
, got this error "pkcs8: incorrect password"
i'm sure my password is correct, so i think maybe something was wrong with password check.
at this line:
Line 204 in 7e063b7
sha1.New
function.
then i go back to check my key file.
openssl asn1parse -in pkcs8.key
0:d=0 hl=4 l=2478 cons: SEQUENCE
4:d=1 hl=2 l= 88 cons: SEQUENCE
6:d=2 hl=2 l= 9 prim: OBJECT :PBES2
17:d=2 hl=2 l= 75 cons: SEQUENCE
19:d=3 hl=2 l= 42 cons: SEQUENCE
21:d=4 hl=2 l= 9 prim: OBJECT :PBKDF2
32:d=4 hl=2 l= 29 cons: SEQUENCE
34:d=5 hl=2 l= 8 prim: OCTET STRING [HEX DUMP]:5624B6B1D069090A
44:d=5 hl=2 l= 3 prim: INTEGER :0F4240
49:d=5 hl=2 l= 12 cons: SEQUENCE
51:d=6 hl=2 l= 8 prim: OBJECT :hmacWithSHA256
61:d=6 hl=2 l= 0 prim: NULL
63:d=3 hl=2 l= 29 cons: SEQUENCE
65:d=4 hl=2 l= 9 prim: OBJECT :aes-256-cbc
As i checked the openssl manual page, it show this option:
-v2prf alg
This option sets the PRF algorithm to use with PKCS#5 v2.0. A typical value value would behmacWithSHA256
. If this option isn't set then the default for the cipher is used orhmacWithSHA256
if there is no default.
ใ
Some implementations may not support custom PRF algorithms and may require thehmacWithSHA1
option to work.
So i tried again with sha1 hash function to convert the key:
openssl pkcs8 -in rsa.key -out pkcs8.key -topk8 -passout pass:xxxxx -v2 aes-256-cbc -iter 4096 -v2prf hmacWithSHA1
ParsePKCS8PrivateKey
worked fine this time.
ใ
Is there any way to auto determine the hash function with sha1 and sha256 ?
Encrypted private key in PKCS#8 format are not supported???
Only PKCS#5 format supported???
Is there any plan in adding the support?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.