无法通过 CNG API 将生成的公钥从 NodeJS 加载到 Windows 中



我使用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的变体:

  1. CERTIFICATE

我们可以使用X509_CERTX509_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;
}
  1. RSA [PRIVATE | PUBLIC] KEY

需要使用CNG_RSA_PRIVATE_KEY_BLOBCNG_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;
}
};
  1. 通用[PRIVATE | PUBLIC] KEY

需要使用PKCS_PRIVATE_KEY_INFOX509_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;
}
  1. 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);
}
};

相关内容

  • 没有找到相关文章