从Python调用PowerShell解密密码遇密钥无效问题求助
问题描述
我正在编写Python脚本,通过调用PowerShell函数解密密码,代码如下:
import subprocess keypwd = "BoFaTnpCv0XYrVxND8mi" pwd= "76492d1116743f0423413b16050a5345MgB8AFUAMQA5AHEAWABvAFMAdwBHAEQASQAyAHcAVABJAHMAbwBKAHAASwBBAEEAPQA9AHwANAA4ADAANwA0ADAANQA5ADEAMgBlADkAZABjAGEAZQA5ADUAZQAyADcAZQBmADAAZAA1AGQAMAAxAGYAMAA0ADQAMQA5ADcAOAAxAGMAYgBlAGYAMwAwADcANwBhADMAMwA0AGMAOQAzAGYAOQA0AGQAMwAwAGMAYgA3ADMAMQA4AGQAYgBlAGUANAAyADQAOQAzADcAMABkADQAMABmADAAMABmADAAYwBkADQAMwBkADMAMABkAGUAOABjAGIA" POWERSHELL = 'powershell -c "{0}"' KEY_DECRYPT_CMD = '''Function KeySet {{ [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'None')] param([string]$string) $length = $string.length $pad = 32-$length $encoding = New-Object System.Text.ASCIIEncoding $bytes = $encoding.GetBytes($string + "0" * $pad) return $bytes }} Function DecryptData ($data, $key) {{ $data | ConvertTo-SecureString -key $key | ForEach-Object {{[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($_))}} }} $setkey = KeySet '{key}'; DecryptData -data '{pwd}' -key $setkey ''' cmd1 = POWERSHELL.format(KEY_DECRYPT_CMD.format(pwd=pwd, key = keypwd)) p = subprocess.Popen(cmd1, stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout,stderr = p.communicate()
单独调用KeySet函数时,Python输出为:
b'66\r\n111\r\n70\r\n97\r\n84\r\n110\r\n112\r\n67\r\n118\r\n48\r\n88\r\n89\r\n114\r\n86\r\n120\r\n78\r\n68\r\n56\r\n109\r\n105\r\n48\r\n'
该输出与PowerShell中的结果一致(仅不含\r\n)。但调用DecryptData函数时出现错误,错误信息如下:
b'ConvertTo-SecureString : The specified key is not valid. Valid key length settings are either 128 bits, 192 bits, or \r\n256 bits. \nAt line:12 char:13\r\n+ $data | ConvertTo-SecureString -key $key |\r\n+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n + CategoryInfo : InvalidArgument: (:) [ConvertTo-SecureString], PSArgumentException\r\n + FullyQualifiedErrorId : ImportSecureString_InvalidArgument,Microsoft.PowerShell.Commands.ConvertToSecureStringCo \r\n mmand\r\n \r\n'
该脚本在PowerShell中可正常运行,问题出在DecryptData函数的密钥传递格式上,请问如何让这段代码正常工作?
解决方法
错误根源是通过字符串格式化传递字节数组时,PowerShell接收的是字符串形式的字节列表,而非实际的字节数组对象。当你在Python里把$setkey通过字符串替换传入时,$key变成了由换行分隔的数字字符串,而ConvertTo-SecureString需要的是[byte[]]类型的密钥。
方案1:在PowerShell脚本内直接生成密钥字节数组(推荐)
修改KEY_DECRYPT_CMD,让DecryptData直接接收密钥字符串,内部调用KeySet生成字节数组,避免跨进程传递类型对象的问题:
import subprocess keypwd = "BoFaTnpCv0XYrVxND8mi" pwd= "76492d1116743f0423413b16050a5345MgB8AFUAMQA5AHEAWABvAFMAdwBHAEQASQAyAHcAVABJAHMAbwBKAHAASwBBAEEAPQA9AHwANAA4ADAANwA0ADAANQA5ADEAMgBlADkAZABjAGEAZQA5ADUAZQAyADcAZQBmADAAZAA1AGQAMAAxAGYAMAA0ADQAMQA5ADcAOAAxAGMAYgBlAGYAMwAwADcANwBhADMAMwA0AGMAOQAzAGYAOQA0AGQAMwAwAGMAYgA3ADMAMQA4AGQAYgBlAGUANAAyADQAOQAzADcAMABkADQAMABmADAAMABmADAAYwBkADQAMwBkADMAMABkAGUAOABjAGIA" POWERSHELL = 'powershell -c "{0}"' # 修改后的脚本:DecryptData直接处理密钥字符串,内部生成字节数组 KEY_DECRYPT_CMD = '''Function KeySet {{ [CmdletBinding(SupportsShouldProcess,ConfirmImpact = 'None')] param([string]$string) $length = $string.length $pad = 32-$length $encoding = New-Object System.Text.ASCIIEncoding $bytes = $encoding.GetBytes($string + "0" * $pad) return $bytes }} Function DecryptData ($data, $keyString) {{ $key = KeySet $keyString $data | ConvertTo-SecureString -key $key | ForEach-Object {{[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($_))}} }} DecryptData -data '{pwd}' -keyString '{key}' ''' cmd1 = POWERSHELL.format(KEY_DECRYPT_CMD.format(pwd=pwd, key = keypwd)) p = subprocess.Popen(cmd1, stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout,stderr = p.communicate() # 处理输出:转码并去除多余换行符 print(stdout.decode('utf-8').strip())
方案2:通过Base64传递字节数组
如果一定要传递字节数组,可以在Python中将密钥字节转为Base64字符串,再在PowerShell中还原为字节数组:
import subprocess import base64 keypwd = "BoFaTnpCv0XYrVxND8mi" pwd= "76492d1116743f0423413b16050a5345MgB8AFUAMQA5AHEAWABvAFMAdwBHAEQASQAyAHcAVABJAHMAbwBKAHAASwBBAEEAPQA9AHwANAA4ADAANwA0ADAANQA5ADEAMgBlADkAZABjAGEAZQA5ADUAZQAyADcAZQBmADAAZAA1AGQAMAAxAGYAMAA0ADQAMQA5ADcAOAAxAGMAYgBlAGYAMwAwADcANwBhADMAMwA0AGMAOQAzAGYAOQA0AGQAMwAwAGMAYgA3ADMAMQA4AGQAYgBlAGUANAAyADQAOQAzADcAMABkADQAMABmADAAMABmADAAYwBkADQAMwBkADMAMABkAGUAOABjAGIA" # 在Python内生成密钥字节数组并转为Base64 encoding = 'ascii' pad_length = 32 - len(keypwd) key_bytes = (keypwd + '0'*pad_length).encode(encoding) key_b64 = base64.b64encode(key_bytes).decode(encoding) POWERSHELL = 'powershell -c "{0}"' KEY_DECRYPT_CMD = '''Function DecryptData ($data, $keyB64) {{ $key = [Convert]::FromBase64String($keyB64) $data | ConvertTo-SecureString -key $key | ForEach-Object {{[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($_))}} }} DecryptData -data '{pwd}' -keyB64 '{key_b64}' ''' cmd1 = POWERSHELL.format(KEY_DECRYPT_CMD.format(pwd=pwd, key_b64=key_b64)) p = subprocess.Popen(cmd1, stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout,stderr = p.communicate() print(stdout.decode('utf-8').strip())
错误原因说明
原代码中,Python通过字符串格式化把$setkey的结果(字节数组的字符串表示)传入DecryptData,此时$key在PowerShell中是一个字符串数组(每个元素是单个字节的数字字符串),而非[byte[]]类型。ConvertTo-SecureString无法识别这种类型,因此抛出密钥长度无效的错误。而直接在PowerShell中运行时,$setkey是真正的[byte[]]对象,所以可以正常工作。
内容的提问来源于stack exchange,提问作者Luka

