导入RSA公钥以在PowerShell 5中进行字符串加密

huangapple go评论109阅读模式
英文:

Import RSA Public key for string encryption in Powershell 5

问题

我正在开发一个非Powershell工具(NodeJS),它将使用Powershell来显示密码提示。对于这个问题的目的,这意味着以下内容:

  • 我使用一个公钥调用Powershell脚本。
  • 公钥将用于加密用户提供的密码。
  • Powershell脚本输出加密后的密码。
  • 我不能使用不随Windows一起提供的Powershell版本,所以我卡在Powershell 5。

我无论如何都无法想出一种将公钥传递到执行加密的类的方法。

我尝试过的一些方法:

$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.ImportFromPem($publicKeyPem)
# 方法调用失败,因为[System.Security.Cryptography.RSACng]不包含名为'ImportFromPem'的方法。

$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.Import()
# 方法调用失败,因为[System.Security.Cryptography.RSACryptoServiceProvider]不包含名为'Import'的方法。

$rsa = [System.Security.Cryptography.AsymmetricAlgorithm]::Create()
$rsa.ImportRSAPublicKey()
# 方法调用失败,因为[System.Security.Cryptography.RSACryptoServiceProvider]不包含名为'ImportRSAPublicKey'的方法。

我确实尝试了更多选项,但所有选项都遇到了相同的问题,即函数不存在。因此,我无法将公钥导入其中。当在Powershell中生成新的密钥对时,我可以成功地进行加密和解密。但这对我来说没有帮助。

这个问题类似于https://stackoverflow.com/questions/76146132/encrypting-and-encoding-a-password-string-with-a-public-key。但该解决方案对我没有帮助,因为它需要Powershell > 5。

我在尝试做一些奇怪的事情吗?毫无疑问,使用现有的公钥进行加密不应该如此困难。

英文:

I am working on a non-Powershell tool (NodeJS) that will use Powershell for displaying a password prompt. For the purposes of this question, that means the following:

  • I invoke the Powershell script with a public key.
  • The public key will be used to encrypt the password given by the user.
  • The Powershell script outputs the encrypted password.
  • I can't use a version of Powershell that does not ship with Windows, so I'm stuck at Powershell 5.

For the life of me, I can't figure out a way to get the public key into a class that does encryption.

Some of the things I tried:

$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.ImportFromPem($publicKeyPem)
# Method invocation failed because [System.Security.Cryptography.RSACng] does not contain a method named 'ImportFromPem'.

$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.Import()
# Method invocation failed because [System.Security.Cryptography.RSACryptoServiceProvider] does not contain a method named 'Import'.

$rsa = [System.Security.Cryptography.AsymmetricAlgorithm]::Create()
$rsa.ImportRSAPublicKey()
# Method invocation failed because [System.Security.Cryptography.RSACryptoServiceProvider] does not contain a method named 'ImportRSAPublicKey'.

I definitely tried more options, all running into the same issue where the function does not exist. Because of this, I can't get it to import the key. When generating a new key pair in Powershell, I can successfully encrypt and decrypt. But that does not help me here.

This question is similar to https://stackoverflow.com/questions/76146132/encrypting-and-encoding-a-password-string-with-a-public-key. But that solution is no help for me since its solution requires Powershell > 5.

I'm I trying to do something weird here? Surely using an existing public key for encryption shouldn't be this difficult.

答案1

得分: 1

根据我所知,Powershell 5 不支持导入 X.509/SPKI 或 PKCS#1 格式的 RSA 公钥(请注意,PKCS#8 是私钥格式),无论是 PEM 编码还是 DER 编码。XML 格式受支持(通过 FromXmlString()),但不受 NodeJS 的 crypto 模块支持。

然而,可以在 NodeJS 中将 RSA 公钥导出为 JWK 格式(自 v15.9.0 起支持3)。JWK 可以轻松转换为 XML 格式,特别是对于公共 RSA 密钥,因为它们只包含两个参数,模数(n)和公共指数(e)。
一旦密钥以 XML 格式存在,就可以使用 FromXmlString() 导入。

以下 Powershell 脚本导入了以 JWK 格式(通过转换为 XML 格式)表示的公钥,并使用 PKCS#1 v1.5 填充进行 RSA 加密:

# 将Base64url转换为Base64的函数
function ToBase64($b64Url) {
    $b64 = $b64Url.Replace('_', '/').Replace('-', '+');
	switch ( $b64Url.Length % 4 )
	{
		2 { $b64 += '==' ; break }
		3 { $b64 += '=' ; break    }
	}
    return $b64
}

# 输入:以JWK格式表示的公共RSA密钥
$publicKeyJwk = '{"kty":"RSA","n":"sucdbBGl3zbrWewgHMYqDiToF9EUDFlTUp7zi8F-Gu1cbkZgY_ZzTZsbsQtWu5QlvCIp75U_tDDqogA5RCgGOLrrgL_1sDQqktG6erkjpNmvRqRk2QQc3fVLBPlPXJVmLDV7zwcZTgBwCl-3gO6V-PndTENi17j5aW697RV44ZFIaFBvv74kb7gas-71NE8mKbahlIwAdssk2xHm0E81CvNwBk3ISuNY_vBrDHLXpjlJqUUe2AHMMO_zO4rWEs-fQ5lSlAEa7Un3wRodR-ETJsra-AP81-vwyPPaCA9jkkKsRATDfvunBWyjwFAVSck_TzOaOeKgyGj7MQ4KnuzGRw"}';

# 提取模数(n)和公共指数(e)
$keyParams = $publicKeyJwk | ConvertFrom-Json
$nB64url = $keyParams.n 
$eB64url = $keyParams.e 

# 将模数和公共指数从Base64url转换为Base64
$nB64 = ToBase64($nB64url);
$eB64 = ToBase64($eB64url);

# 创建以XML格式表示的公共RSA密钥
$publicKeyXml = "<RSAKeyValue><Modulus>" + $nB64 + "</Modulus><Exponent>" + $eB64 + "</Exponent></RSAKeyValue>";
Write-Output $publicKeyXml;

# 导入密钥 
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.FromXmlString($publicKeyXml)

# 使用RSA和PKCS#1 v1.5填充进行加密
$plaintext = "The quick brown fox jumps over the lazy dog"
[byte[]] $plaintextBytes = [Text.Encoding]::UTF8.GetBytes($plaintext) 
$ciphertext = $rsa.Encrypt($plaintextBytes, [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1)
$ciphertextB64 = [System.Convert]::ToBase64String($ciphertext)

#输出Base64编码的密文
Write-Output $ciphertextB64

可以通过以下NodeJS代码生成RSA密钥对进行测试:

var crypto = require('crypto')

var { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {modulusLength: 2048})
var pubkeyjwk = publicKey.export({format:'jwk'})
console.log(JSON.stringify(pubkeyjwk))

var privatejwk = privateKey.export({type:'pkcs1', format:'pem'})
console.log(privatejwk)

NodeJS代码将公钥导出为JWK格式,将私钥导出为PEM编码的PKCS#1密钥。

导出的公钥可以在上述Powershell脚本中使用。
Powershell脚本生成的密文可以使用适当的代码/工具(例如CyberChef)使用私钥解密,从而证明了Powershell脚本的正确加密。

英文:

As far as I know, Powershell 5 does not support importing RSA public keys in X.509/SPKI or PKCS#1 format (note that PKCS#8 is a private key format), neither PEM nor DER encoded.
XML is supported (via FromXmlString()), but not by the crypto module of NodeJS.

However, it is possible to export the RSA public key in NodeJS in JWK format (supported since v15.9.0). The JWK can be easily converted to the XML format, especially for public RSA keys, since they contain only two parameters, the modulus (n) and the public exponent (e).
Once the key is in XML format, it can be imported with FromXmlString().

The following Powershell script imports a public key in JWK format (via conversion to the XML format) and performs an RSA encryption with PKCS#1 v1.5 padding:

# Function to convert from Base64url to Base64
function ToBase64($b64Url) {
    $b64 = $b64Url.Replace(&#39;_&#39;, &#39;/&#39;).Replace(&#39;-&#39;, &#39;+&#39;);
	switch ( $b64Url.Length % 4 )
	{
		2 { $b64 += &quot;==&quot;; break }
		3 { $b64 += &quot;=&quot;; break    }
	}
    return $b64
}

# Input: Public RSA key as JWK
$publicKeyJwk = &#39;{&quot;kty&quot;:&quot;RSA&quot;,&quot;n&quot;:&quot;sucdbBGl3zbrWewgHMYqDiToF9EUDFlTUp7zi8F-Gu1cbkZgY_ZzTZsbsQtWu5QlvCIp75U_tDDqogA5RCgGOLrrgL_1sDQqktG6erkjpNmvRqRk2QQc3fVLBPlPXJVmLDV7zwcZTgBwCl-3gO6V-PndTENi17j5aW697RV44ZFIaFBvv74kb7gas-71NE8mKbahlIwAdssk2xHm0E81CvNwBk3ISuNY_vBrDHLXpjlJqUUe2AHMMO_zO4rWEs-fQ5lSlAEa7Un3wRodR-ETJsra-AP81-vwyPPaCA9jkkKsRATDfvunBWyjwFAVSck_TzOaOeKgyGj7MQ4KnuzGRw&quot;,&quot;e&quot;:&quot;AQAB&quot;}&#39;;

# Extract modulus (n) and public exponent (e)
$keyParams = $publicKeyJwk | ConvertFrom-Json
$nB64url = $keyParams.n 
$eB64url = $keyParams.e 

# Convert modulus and public exponent from Base64url to Base64
$nB64 = ToBase64($nB64url);
$eB64 = ToBase64($eB64url);

# Create public RSA key in XML format
$publicKeyXml = &quot;&lt;RSAKeyValue&gt;&lt;Modulus&gt;&quot; + $nB64 + &quot;&lt;/Modulus&gt;&lt;Exponent&gt;&quot; + $eB64 + &quot;&lt;/Exponent&gt;&lt;/RSAKeyValue&gt;&quot;;
Write-Output $publicKeyXml;

# Import key 
$rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider
$rsa.FromXmlString($publicKeyXml)

# Encrypt with RSA and PKCS#1 v1.5 padding
$plaintext = &quot;The quick brown fox jumps over the lazy dog&quot;
[byte[]] $plaintextBytes = [Text.Encoding]::UTF8.GetBytes($plaintext) 
$ciphertext = $rsa.Encrypt($plaintextBytes, [System.Security.Cryptography.RSAEncryptionPadding]::Pkcs1)
$ciphertextB64 = [System.Convert]::ToBase64String($ciphertext)

#Output Base64 encoded ciphertext
Write-Output $ciphertextB64

A test is possible by generating an RSA key pair with the following NodeJS code:

var crypto = require(&#39;crypto&#39;)

var { privateKey, publicKey } = crypto.generateKeyPairSync(&#39;rsa&#39;, {modulusLength: 2048})
var pubkeyjwk = publicKey.export({format:&#39;jwk&#39;})
console.log(JSON.stringify(pubkeyjwk))

var privatejwk = privateKey.export({type:&#39;pkcs1&#39;, format:&#39;pem&#39;})
console.log(privatejwk)

The NodeJS code exports the public key as JWK and the private key as PEM encoded PKCS#1 key.

The exported public key can be used in the above Powershell script.
The ciphertext generated by the Powershell script can be decrypted with the private key using a suitable code/tool (e.g. CyberChef), proving the correct encryption by the Powershell script.

huangapple
  • 本文由 发表于 2023年7月20日 22:19:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76730844.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定