我使用crypto
库在NodeJS中生成RSA公钥/私钥对:
crypto.generateKeyPair(
"rsa",
{
modulusLength: 1024,
publicKeyEncoding: {
type: "pkcs1",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs1",
format: "pem",
},
},
(err, publicKey, privateKey) => {
fs.writeFileSync("/home/dev/priv.pem", privateKey);
fs.writeFileSync("/home/dev/pub.pem", publicKey);
} );
现在,当我想使用Windows加密压缩天然气api加载公钥时,它失败了。CryptDecodeObjectEx
返回0x8009310b (ASN1坏标签值错误)。我使用这个链接来加载公钥。你知道我做错了什么吗?我应该以特定的方式生成密钥吗?
请替换"pkcs1"带有"spki"的字符串。该代码不需要RSA密钥,它需要一个SubjectPublicKeyInfo编码的密钥。
SPKI是在X.509标准中定义的,它允许ASN.1编码表示任何类型的密钥。例如,它还可以包含基于椭圆曲线的各种类型的密钥。它内部包含pkcs# 1编码的密钥,但在这种情况下,密钥类型的指示符(在这种情况下是RSA密钥)是二进制编码而不是文本编码。指出:
- 您不应该使用"教科书式的rsa",而应该始终使用某种安全填充,例如pkcs# 1填充或OAEP填充。
BCRYPT_PAD_NONE
只有在您定义自己的方案或实现另一个现有方案时才有用,最好避免这种情况。如果您想加密更大的消息,请考虑混合加密(例如加密AES密钥)。 - 1024位密钥现在被认为太小了;请使用至少2048位的密钥。 你可能想看看容器格式,如CMS甚至PGP,而不是直接执行RSA加密。
不同的PEM块(-----BEGIN X-----
…-----END X-----
),有不同的格式。所以需要在调用CryptDecodeObjectEx
的地方使用不同的常量lpszStructType。common -首先我们需要将base64转换为二进制(使用CryptStringToBinaryA
)。然后使用lpszStructType基于X
调用CryptDecodeObjectEx
。试着描述一些X
的变体:
CERTIFICATE
我们可以使用X509_CERT
和X509_CERT_TO_BE_SIGNED
来获取指向CERT_INFO结构的指针,或者如果我们不需要get签名,就使用X509_CERT_TO_BE_SIGNED
。但在大多数情况下-更简单地-调用CertCreateCertificateContext
,内部调用CryptDecodeObjectEx
导入不同的键类型,我们可以使用一些util类:
inline ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
class __declspec(novtable) Pem
{
virtual HRESULT process(_In_reads_(cb) BYTE* pb, _In_ ULONG cb) = 0;
public:
HRESULT import(_In_reads_(cch) PCSTR psz, _In_ ULONG cch)
{
ULONG dwError;
PBYTE pb = 0;
ULONG cb = 0;
while (NOERROR == (dwError = BOOL_TO_ERROR(CryptStringToBinaryA(psz, cch, CRYPT_STRING_BASE64, pb, &cb, 0, 0))))
{
if (pb)
{
dwError = process(pb, cb);
break;
}
if (!(pb = new UCHAR[cb]))
{
dwError = ERROR_OUTOFMEMORY;
break;
}
}
if (pb) delete [] pb;
return HRESULT_FROM_WIN32(dwError);
}
};
class __declspec(novtable) PemKey : public Pem
{
protected:
BCRYPT_KEY_HANDLE _M_hKey = 0;
virtual ~PemKey()
{
if (_M_hKey)
{
BCryptDestroyKey(_M_hKey);
}
}
};
NTSTATUS ImportCngKey(_Out_ BCRYPT_KEY_HANDLE* phKey, _In_ PCWSTR pszBlobType, _In_reads_(cb) BYTE* pb, _In_ ULONG cb)
{
BCRYPT_ALG_HANDLE hAlgorithm;
NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, BCRYPT_RSA_ALGORITHM, 0, 0);
if (0 <= status)
{
status = BCryptImportKeyPair(hAlgorithm, 0, pszBlobType, phKey, pb, cb, 0);
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
}
return status;
}
RSA [PRIVATE | PUBLIC] KEY
需要使用CNG_RSA_PRIVATE_KEY_BLOB
和CNG_RSA_PUBLIC_KEY_BLOB
struct __declspec(novtable) PemRsaKey : public PemKey
{
virtual PCSTR GetStructType() = 0;
virtual PCWSTR GetBlobType() = 0;
virtual HRESULT process(_In_reads_(cb) BYTE* pb, _In_ ULONG cb)
{
if (CryptDecodeObjectEx(X509_ASN_ENCODING, GetStructType(), pb, cb,
CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG, 0, &pb, &cb))
{
NTSTATUS status = ImportCngKey(&_M_hKey, GetBlobType(), pb, cb);
LocalFree(pb);
return status;
}
return HRESULT_FROM_WIN32(GetLastError());
}
};
struct PemRsaPublicKey : public PemRsaKey
{
virtual PCSTR GetStructType()
{
return CNG_RSA_PUBLIC_KEY_BLOB;
}
virtual PCWSTR GetBlobType()
{
return BCRYPT_RSAPUBLIC_BLOB;
}
};
struct PemRsaPrivateKey : public PemRsaKey
{
virtual PCSTR GetStructType()
{
return CNG_RSA_PRIVATE_KEY_BLOB;
}
virtual PCWSTR GetBlobType()
{
return BCRYPT_RSAPRIVATE_BLOB;
}
};
- 通用
[PRIVATE | PUBLIC] KEY
需要使用PKCS_PRIVATE_KEY_INFO
和X509_PUBLIC_KEY_INFO
。然后我们可以在调用CryptImportPublicKeyInfoEx2
时使用PCERT_PUBLIC_KEY_INFO
。在PCRYPT_PRIVATE_KEY_INFO
的情况下,我不知道是否存在将其导入CNG密钥的通用api,可能需要手动查找PrivateKeyInfo->Algorithm.pszObjId
,并基于它对CryptDecodeObjectEx
进行第二次调用。对于szOID_RSA_RSA
需要使用CNG_RSA_PRIVATE_KEY_BLOB
,对于ECC -szOID_ECC_PUBLIC_KEY
struct PemPrivateKey : public PemKey
{
virtual HRESULT process(_In_reads_(cb) BYTE* pb, _In_ ULONG cb)
{
PCRYPT_PRIVATE_KEY_INFO PrivateKeyInfo;
HRESULT dwError;
if (NOERROR == (dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(
X509_ASN_ENCODING, PKCS_PRIVATE_KEY_INFO, pb, cb,
CRYPT_DECODE_ALLOC_FLAG|CRYPT_DECODE_NOCOPY_FLAG|CRYPT_DECODE_SHARE_OID_STRING_FLAG,
0, &PrivateKeyInfo, &cb))))
{
if (!strcmp(PrivateKeyInfo->Algorithm.pszObjId, szOID_RSA_RSA))
{
if (NOERROR == (dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(
X509_ASN_ENCODING, CNG_RSA_PRIVATE_KEY_BLOB,
PrivateKeyInfo->PrivateKey.pbData, PrivateKeyInfo->PrivateKey.cbData,
CRYPT_DECODE_ALLOC_FLAG, 0, &pb, &cb))))
{
dwError = ImportCngKey(&_M_hKey, BCRYPT_RSAPRIVATE_BLOB, pb, cb);
LocalFree(pb);
}
}
else if (!strcmp(PrivateKeyInfo->Algorithm.pszObjId, szOID_ECC_PUBLIC_KEY))
{
PCRYPT_ECC_PRIVATE_KEY_INFO p;
if (NOERROR == (dwError = BOOL_TO_ERROR(CryptDecodeObjectEx(
X509_ASN_ENCODING, X509_ECC_PRIVATE_KEY,
PrivateKeyInfo->PrivateKey.pbData, PrivateKeyInfo->PrivateKey.cbData,
CRYPT_DECODE_ALLOC_FLAG, 0, &p, &cb))))
{
dwError = ImportECCKey(&_M_hKey, p);
LocalFree(p);
}
}
else
{
dwError = STATUS_NOT_IMPLEMENTED;
}
LocalFree(PrivateKeyInfo);
}
return HRESULT_FROM_WIN32(dwError);
}
};
NTSTATUS ImportECCKey(_Out_ BCRYPT_KEY_HANDLE* phKey, _In_ PCRYPT_ECC_PRIVATE_KEY_INFO p)
{
ULONG cbKey = p->PrivateKey.cbData;
if (1 + (cbKey << 1) != p->PublicKey.cbData)
{
return STATUS_INVALID_PARAMETER;
}
PCWSTR pszAlgId;
ULONG dwMagic;
switch (cbKey)
{
case 256/8:
pszAlgId = BCRYPT_ECDSA_P256_ALGORITHM;
dwMagic = BCRYPT_ECDSA_PRIVATE_P256_MAGIC;
break;
case 384/8:
pszAlgId = BCRYPT_ECDSA_P384_ALGORITHM;
dwMagic = BCRYPT_ECDSA_PRIVATE_P384_MAGIC;
break;
case 528/8:
pszAlgId = BCRYPT_ECDSA_P521_ALGORITHM;
dwMagic = BCRYPT_ECDSA_PRIVATE_P521_MAGIC;
break;
default:
return NTE_NOT_SUPPORTED;
}
ULONG cb = sizeof(BCRYPT_ECCKEY_BLOB) + cbKey * 3;
PBCRYPT_ECCKEY_BLOB pecc = (PBCRYPT_ECCKEY_BLOB)alloca(cb);
pecc->cbKey = cbKey;
pecc->dwMagic = dwMagic;
PBYTE pb = (PBYTE)(pecc+1);
memcpy(pb, p->PublicKey.pbData + 1, cbKey << 1);
memcpy(pb + (cbKey << 1), p->PrivateKey.pbData, cbKey);
BCRYPT_ALG_HANDLE hAlgorithm;
NTSTATUS status = BCryptOpenAlgorithmProvider(&hAlgorithm, pszAlgId, 0, 0);
if (0 <= status)
{
status = BCryptImportKeyPair(hAlgorithm, 0, BCRYPT_ECCPRIVATE_BLOB, phKey, (PUCHAR)pecc, cb, 0);
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
}
return status;
}
ENCRYPTED PRIVATE KEY
需要使用PKCS_ENCRYPTED_PRIVATE_KEY_INFO
结构类型,得到CRYPT_ENCRYPTED_PRIVATE_KEY_INFO
。这里有两种方法:更简单但更慢(所有调用都是RPC到类)-使用加密api。或者快速且无记录。所以我们可以用or
struct PemEncryptedPrivateKey : public PemPrivateKey
{
PCWSTR _M_pszPassword;
PemEncryptedPrivateKey(PCWSTR pszPassword) : _M_pszPassword(pszPassword)
{
}
virtual HRESULT process2(_In_reads_(cb) BYTE* pb, _In_ ULONG cb)
{
NCRYPT_PROV_HANDLE hProvider;
SECURITY_STATUS status = NCryptOpenStorageProvider(&hProvider, MS_KEY_STORAGE_PROVIDER, 0);
if (NOERROR == status)
{
NCryptBuffer buf = {
(1 + (ULONG)wcslen(_M_pszPassword)) * sizeof(WCHAR),
NCRYPTBUFFER_PKCS_SECRET,
const_cast<PWSTR>(_M_pszPassword)
};
NCryptBufferDesc ParameterList { NCRYPTBUFFER_VERSION, 1, &buf };
NCRYPT_KEY_HANDLE hKey;
status = NCryptImportKey(hProvider, 0, NCRYPT_PKCS8_PRIVATE_KEY_BLOB,
&ParameterList, &hKey, (PBYTE)pb, cb, NCRYPT_DO_NOT_FINALIZE_FLAG);
NCryptFreeObject(hProvider);
if (NOERROR == status)
{
static const ULONG flags = NCRYPT_ALLOW_EXPORT_FLAG|NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
if (NOERROR == (status = NCryptSetProperty(hKey, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&flags, sizeof(flags), 0)) &&
NOERROR == (status = NCryptFinalizeKey(hKey, NCRYPT_SILENT_FLAG)))
{
cb = 0, pb = 0;
while (NOERROR == (status = NCryptExportKey(hKey, 0, BCRYPT_PRIVATE_KEY_BLOB, 0, pb, cb, &cb, 0)))
{
if (pb)
{
status = ImportCngKey(&_M_hKey, BCRYPT_PRIVATE_KEY_BLOB, pb, cb);
break;
}
if (!(pb = new UCHAR[cb]))
{
status = ERROR_OUTOFMEMORY;
break;
}
}
if (pb)
{
delete [] pb;
}
}
NCryptFreeObject(hKey);
}
}
return HRESULT_FROM_WIN32(status);
}
};
或这样的代码:
struct ENCODE_DECODE_PARA {
PFN_CRYPT_ALLOC pfnAlloc;
PFN_CRYPT_FREE pfnFree;
};
struct CryptCNGMap
{
ULONG version;
NTSTATUS (WINAPI * HashInit)(
_Out_ BCRYPT_ALG_HANDLE* phAlgorithm,
_Out_ ULONG* pcbObjectLength,
_In_ ULONG dwFlags);
NTSTATUS (WINAPI *AlgorithmInit_)(
_Out_ BCRYPT_ALG_HANDLE* phAlgorithm,
_Out_ ULONG* pcbObjectLength,
_In_ BYTE* pbParams,
_In_ ULONG cbParams
);
NTSTATUS (WINAPI *AlgorithmInit)(
_Out_ BCRYPT_ALG_HANDLE* phAlgorithm,
_Out_ ULONG* pcbObjectLength,
_In_ BYTE* pbParams,
_In_ ULONG cbParams
);
NTSTATUS (WINAPI *KeyInit_)(
_In_ BCRYPT_ALG_HANDLE hAlgorithm,
_Out_ BCRYPT_KEY_HANDLE* phKey,
_Out_ BYTE* pbKeyObject,
_In_ ULONG cbKeyObject,
_In_ const BYTE* pbSecret,
_In_ ULONG cbSecret,
_Out_ BYTE* pbParams,
_Out_ ULONG cbParams
);
NTSTATUS (WINAPI *KeyInit)(
_In_ BCRYPT_ALG_HANDLE hAlgorithm,
_Out_ BCRYPT_KEY_HANDLE* phKey,
_Out_ BYTE* pbKeyObject,
_In_ ULONG cbKeyObject,
_In_ const BYTE* pbSecret,
_In_ ULONG cbSecret,
_Out_ BYTE* pbParams,
_Out_ ULONG cbParams
);
NTSTATUS (WINAPI *PasswordDeriveKey)(/* dont know */);
NTSTATUS (WINAPI *ParamsEncode)(
_In_ const BYTE* pb,
_In_ ULONG cb,
_In_ const ENCODE_DECODE_PARA* pEncodePara,
_Out_ BYTE** ppbEncoded,
_Out_ ULONG* pcbEncoded
);
NTSTATUS (WINAPI *ParamsDecode)(
_In_ const BYTE* pbEncoded,
_In_ ULONG cbEncoded,
_In_ const ENCODE_DECODE_PARA* pDecodePara,
_Out_ BYTE** ppb,
_Out_ ULONG* pcb
);
};
void* WINAPI PkiAlloc(_In_ size_t cbSize)
{
return LocalAlloc(LMEM_FIXED, cbSize);
}
void WINAPI PkiFree(void* pv)
{
LocalFree(pv);
}
struct PemEncryptedPrivateKey : public PemPrivateKey
{
PCWSTR _M_pszPassword;
PemEncryptedPrivateKey(PCWSTR pszPassword) : _M_pszPassword(pszPassword)
{
}
virtual HRESULT process(_In_reads_(cb) BYTE* pb, _In_ ULONG cb)
{
NTSTATUS status;
if (HCRYPTOIDFUNCSET hFuncSet = CryptInitOIDFunctionSet("CryptCNGPKCS12GetMap", 0))
{
PCRYPT_ENCRYPTED_PRIVATE_KEY_INFO pepki;
if (NOERROR == (status = BOOL_TO_ERROR(CryptDecodeObjectEx(
X509_ASN_ENCODING, PKCS_ENCRYPTED_PRIVATE_KEY_INFO,
pb, cb, CRYPT_DECODE_ALLOC_FLAG, 0, &pepki, &cb))))
{
HCRYPTOIDFUNCADDR hFuncAddr;
union {
PVOID pvFuncAddr;
CryptCNGMap* (WINAPI *GetPKCS12Map)();
};
if (NOERROR == (status = BOOL_TO_ERROR(CryptGetOIDFunctionAddress(hFuncSet, X509_ASN_ENCODING,
pepki->EncryptionAlgorithm.pszObjId, CRYPT_GET_INSTALLED_OID_FUNC_FLAG, &pvFuncAddr, &hFuncAddr))))
{
CryptCNGMap* map = GetPKCS12Map();
static const ENCODE_DECODE_PARA cdp = { PkiAlloc, PkiFree };
if (0 <= (status = map->ParamsDecode(
pepki->EncryptionAlgorithm.Parameters.pbData,
pepki->EncryptionAlgorithm.Parameters.cbData,
&cdp,
&pepki->EncryptionAlgorithm.Parameters.pbData,
&pepki->EncryptionAlgorithm.Parameters.cbData)))
{
BCRYPT_ALG_HANDLE hAlgorithm;
if (0 <= (status = map->AlgorithmInit(&hAlgorithm, &cb,
pepki->EncryptionAlgorithm.Parameters.pbData,
pepki->EncryptionAlgorithm.Parameters.cbData)))
{
BCRYPT_KEY_HANDLE hKey;
status = map->KeyInit(hAlgorithm, &hKey,
(PBYTE)alloca(cb), cb,
(PBYTE)_M_pszPassword,
(1+(ULONG)wcslen(_M_pszPassword))*sizeof(WCHAR),
pepki->EncryptionAlgorithm.Parameters.pbData,
pepki->EncryptionAlgorithm.Parameters.cbData
);
BCryptCloseAlgorithmProvider(hAlgorithm, 0);
if (0 <= status)
{
status = BCryptDecrypt(hKey,
pepki->EncryptedPrivateKey.pbData,
pepki->EncryptedPrivateKey.cbData,
0, 0, 0,
pepki->EncryptedPrivateKey.pbData,
pepki->EncryptedPrivateKey.cbData,
&pepki->EncryptedPrivateKey.cbData, 0);
BCryptDestroyKey(hKey);
}
}
LocalFree(pepki->EncryptionAlgorithm.Parameters.pbData);
}
CryptFreeOIDFunctionAddress(hFuncAddr, 0);
}
if (0 <= status)
{
status = PemPrivateKey::process(
pepki->EncryptedPrivateKey.pbData,
pepki->EncryptedPrivateKey.cbData);
}
LocalFree(pepki);
}
}
else
{
status = GetLastError();
}
return HRESULT_FROM_WIN32(status);
}
};