PowerShell 和 Python 中的 AES 加密
AES Encryption in PowerShell and Python
我的目标是能够在 PowerShell 中对字符串进行 AES 加密,将其发送到 python 可用的 UNIX 系统,然后将字符串解密回纯文本。我也希望能够做相反的事情。我不是加密专家或 PowerShell/python 程序员,但这是到目前为止我能够使用代码做的事情:
function Create-AesManagedObject($key, $IV) {
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
if ($IV) {
if ($IV.getType().Name -eq "String") {
$aesManaged.IV = [System.Convert]::FromBase64String($IV)
}
else {
$aesManaged.IV = $IV
}
}
if ($key) {
if ($key.getType().Name -eq "String") {
$aesManaged.Key = [System.Convert]::FromBase64String($key)
}
else {
$aesManaged.Key = $key
}
}
$aesManaged
}
function Encrypt-String($key, $unencryptedString) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
$aesManaged = Create-AesManagedObject $key $IV
$encryptor = $aesManaged.CreateEncryptor()
$encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
[byte[]] $fullData = $aesManaged.IV + $encryptedData
$aesManaged.Dispose()
[System.Convert]::ToBase64String($fullData)
}
function Decrypt-String($key, $encryptedStringWithIV) {
$bytes = [System.Convert]::FromBase64String($encryptedStringWithIV)
$IV = $bytes[0..15]
$aesManaged = Create-AesManagedObject $key $IV
$decryptor = $aesManaged.CreateDecryptor();
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
$aesManaged.Dispose()
[System.Text.Encoding]::UTF8.GetString($unencryptedData).Trim([char]0)
}
# key passphrase is a 16 byte string that is used to create the AES key.
$key_passphrase = "MypassphraseKey1"
# base64 encode the key. The resulting key should be exactly 44 characters (43 characters with a single = of padding) (256 bits)
$Bytes = [System.Text.Encoding]::Ascii.GetBytes($key_passphrase)
$key =[Convert]::ToBase64String($Bytes)
# init is used to create the IV
$init = "This is an IV123"
# converts init to a byte array (e.g. T = 84, h = 104) and then sha1 hash it
$IV = (new-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]
write-output "IV is equal to $IV"
write-output "AES key is $key"
$unencryptedString = "testing"
$encryptedString = Encrypt-String $key $unencryptedString
$backToPlainText = Decrypt-String $key $encryptedString
write-output "Unencrypted string: $unencryptedString"
write-output "Encrypted string: $encryptedString"
write-output "Unencrytped string: $backToPlainText"
PowerShell 脚本似乎可以很好地进行加密和解密。对于 python 端,我可以定义相同的 AES 密钥值,因为它只是我的密钥密码的 base64 编码。但是,我在执行时没有得到相同的字符串加密值(例如 PowerShell 输出 UXKWIhtaUgFOvN13bvA4tx4+2Hjkv4v6I1G3Xfl6zp0= 和 Python 输出 BOJ3Ox4fJxR+jFZ0CBQ25Q==)。我相信这些需要匹配才能解密,但我可能会弄错。我知道设置静态 IV 和密钥会使其不安全,但我愿意这样做以便能够跨平台加密和解密(除非有更好的方法使用 AES)。任何帮助将不胜感激。
Python代码
import base64, array
import Crypto
import Crypto.Random
from Crypto.Cipher import AES
def pad_data(data):
if len(data) % 16 == 0:
return data
databytes = bytearray(data)
padding_required = 15 - (len(databytes) % 16)
databytes.extend(b'\x80')
databytes.extend(b'\x00' * padding_required)
return bytes(databytes)
def unpad_data(data):
if not data:
return data
data = data.rstrip(b'\x00')
if data[-1] == 128: # b'\x80'[0]:
return data[:-1]
else:
return data
def encrypt(key, iv, data):
aes = AES.new(key, AES.MODE_CBC, iv)
data = pad_data(data)
return aes.encrypt(data)
def decrypt(key, iv, data):
aes = AES.new(key, AES.MODE_CBC, iv)
data = aes.decrypt(data)
return unpad_data(data)
def test_crypto ():
key = "MypassphraseKey1"
# found using the debugger in the PowerShell ISE to get the value byte value which was converted to hex
iv = "\x51\x72\x96\x22\x1b\x5a\x52\x01\x4e\xbc\xdd\x77\x6e\xf0\x38\xb7"
msg = b"testing"
# hex value of IV in powershell script is 51 72 96 22 1b 5a 52 01 4e bc dd 77 6e f0 38 b7
print("Value of IV: " + iv)
# base64 encode key
b64key = base64.b64encode(key)
print("AES key encoded: " + b64key)
code = encrypt(key, iv, msg)
# convert encrypted string to base64
b64encoded = base64.b64encode(code)
print("Encrypted string: " + b64encoded)
decoded = decrypt(key, iv, code)
print("Decoded: " + decoded)
if __name__ == '__main__':
test_crypto()
一些建议:
- 16 个字符的 ASCII 字符串是
128^16 = 5.19229686e33
可能的键输入。 Base64 编码 16 个字节产生 24 个字节 (4*ceil(16/3)
)。因此,即使您使用的是 192 位 AES 密钥(理论上 6.27710174e57
键组合),您也只能使用其中的 1/1208925820422879877545683
[万亿分之一]。事实上,您将密钥大小设置为 256 位,显然代码忽略了 that/allowing 192 位密钥而没有错误。
使用 Rfc2898DeriveBytes
to derive your AES key rather than a Base64 transformation of a raw string. RFC 2898 定义 PBKDF2(Password-Based 密钥派生函数 2),一个 HMAC-based 密钥派生函数来安全地从密码派生加密密钥,并提供 HMAC/SHA1与大量迭代一起使用以减轻对您的密钥的 brute-force 攻击。
您只是在 PowerShell 中调用 TransformFinalBlock()
进行加密和解密。我想如果消息长于一个块(16 字节),这将无法加密或解密完整的消息。尝试使用 This is a plaintext message.
(29 字节)这样的输入消息。我相信你想同时使用 TransformBlock()
和 TransformFinalBlock()
.
静态 IV 不安全的说法是正确的(违背了 IV 的目的,它应该是唯一的,并且 non-predictable 对于每个使用相同密钥的加密操作)。 AesManaged
已经提供了一种方法 GenerateIV()
来生成令人满意的 IV,您可以从 IV 属性 访问它并添加到密文中。
您的 Base64 编码密文的 PowerShell 输出为 44 个字符(Base64 中的16 byte IV + 16 byte ciphered message = 32 bytes -> 44 bytes
)。您的 Python Base64 输出是 24 个字符(Base64 中的 16 bytes -> 24 bytes
)。此输出不包括 IV 或消息(或其他不太可能导致输出受限的原因)。查看代码,您的 encrypt
方法不会将 IV 添加到密文中。
最后,在这一点上,您的代码应该可以工作并且内部一致并且 cross-compatible。在这里您应该重新审视几个设计决策:
零填充是 non-standard,虽然您手动实现了它,但更需要像 PKCS #5/#7
这样的 well-defined 填充方案。在 Python 和 .NET.
中都有丰富的实现和代码示例来实现这一点
您正在使用CBC block cipher mode of operation。虽然 CBC 可以保密,但它不提供完整性。您应该使用经过身份验证的加密模式 (AE/AEAD),例如 GCM 或 EAX。如果不能,请使用 HMAC 结构(如 HMAC/SHA-256 和 与您的加密密钥不同 共享秘密,并使用 constant-time 方法 [=45= 验证 MAC ]在尝试解密之前。
我的目标是能够在 PowerShell 中对字符串进行 AES 加密,将其发送到 python 可用的 UNIX 系统,然后将字符串解密回纯文本。我也希望能够做相反的事情。我不是加密专家或 PowerShell/python 程序员,但这是到目前为止我能够使用代码做的事情:
function Create-AesManagedObject($key, $IV) {
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
if ($IV) {
if ($IV.getType().Name -eq "String") {
$aesManaged.IV = [System.Convert]::FromBase64String($IV)
}
else {
$aesManaged.IV = $IV
}
}
if ($key) {
if ($key.getType().Name -eq "String") {
$aesManaged.Key = [System.Convert]::FromBase64String($key)
}
else {
$aesManaged.Key = $key
}
}
$aesManaged
}
function Encrypt-String($key, $unencryptedString) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
$aesManaged = Create-AesManagedObject $key $IV
$encryptor = $aesManaged.CreateEncryptor()
$encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
[byte[]] $fullData = $aesManaged.IV + $encryptedData
$aesManaged.Dispose()
[System.Convert]::ToBase64String($fullData)
}
function Decrypt-String($key, $encryptedStringWithIV) {
$bytes = [System.Convert]::FromBase64String($encryptedStringWithIV)
$IV = $bytes[0..15]
$aesManaged = Create-AesManagedObject $key $IV
$decryptor = $aesManaged.CreateDecryptor();
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
$aesManaged.Dispose()
[System.Text.Encoding]::UTF8.GetString($unencryptedData).Trim([char]0)
}
# key passphrase is a 16 byte string that is used to create the AES key.
$key_passphrase = "MypassphraseKey1"
# base64 encode the key. The resulting key should be exactly 44 characters (43 characters with a single = of padding) (256 bits)
$Bytes = [System.Text.Encoding]::Ascii.GetBytes($key_passphrase)
$key =[Convert]::ToBase64String($Bytes)
# init is used to create the IV
$init = "This is an IV123"
# converts init to a byte array (e.g. T = 84, h = 104) and then sha1 hash it
$IV = (new-Object Security.Cryptography.SHA1Managed).ComputeHash( [Text.Encoding]::UTF8.GetBytes($init) )[0..15]
write-output "IV is equal to $IV"
write-output "AES key is $key"
$unencryptedString = "testing"
$encryptedString = Encrypt-String $key $unencryptedString
$backToPlainText = Decrypt-String $key $encryptedString
write-output "Unencrypted string: $unencryptedString"
write-output "Encrypted string: $encryptedString"
write-output "Unencrytped string: $backToPlainText"
PowerShell 脚本似乎可以很好地进行加密和解密。对于 python 端,我可以定义相同的 AES 密钥值,因为它只是我的密钥密码的 base64 编码。但是,我在执行时没有得到相同的字符串加密值(例如 PowerShell 输出 UXKWIhtaUgFOvN13bvA4tx4+2Hjkv4v6I1G3Xfl6zp0= 和 Python 输出 BOJ3Ox4fJxR+jFZ0CBQ25Q==)。我相信这些需要匹配才能解密,但我可能会弄错。我知道设置静态 IV 和密钥会使其不安全,但我愿意这样做以便能够跨平台加密和解密(除非有更好的方法使用 AES)。任何帮助将不胜感激。
Python代码
import base64, array
import Crypto
import Crypto.Random
from Crypto.Cipher import AES
def pad_data(data):
if len(data) % 16 == 0:
return data
databytes = bytearray(data)
padding_required = 15 - (len(databytes) % 16)
databytes.extend(b'\x80')
databytes.extend(b'\x00' * padding_required)
return bytes(databytes)
def unpad_data(data):
if not data:
return data
data = data.rstrip(b'\x00')
if data[-1] == 128: # b'\x80'[0]:
return data[:-1]
else:
return data
def encrypt(key, iv, data):
aes = AES.new(key, AES.MODE_CBC, iv)
data = pad_data(data)
return aes.encrypt(data)
def decrypt(key, iv, data):
aes = AES.new(key, AES.MODE_CBC, iv)
data = aes.decrypt(data)
return unpad_data(data)
def test_crypto ():
key = "MypassphraseKey1"
# found using the debugger in the PowerShell ISE to get the value byte value which was converted to hex
iv = "\x51\x72\x96\x22\x1b\x5a\x52\x01\x4e\xbc\xdd\x77\x6e\xf0\x38\xb7"
msg = b"testing"
# hex value of IV in powershell script is 51 72 96 22 1b 5a 52 01 4e bc dd 77 6e f0 38 b7
print("Value of IV: " + iv)
# base64 encode key
b64key = base64.b64encode(key)
print("AES key encoded: " + b64key)
code = encrypt(key, iv, msg)
# convert encrypted string to base64
b64encoded = base64.b64encode(code)
print("Encrypted string: " + b64encoded)
decoded = decrypt(key, iv, code)
print("Decoded: " + decoded)
if __name__ == '__main__':
test_crypto()
一些建议:
- 16 个字符的 ASCII 字符串是
128^16 = 5.19229686e33
可能的键输入。 Base64 编码 16 个字节产生 24 个字节 (4*ceil(16/3)
)。因此,即使您使用的是 192 位 AES 密钥(理论上6.27710174e57
键组合),您也只能使用其中的1/1208925820422879877545683
[万亿分之一]。事实上,您将密钥大小设置为 256 位,显然代码忽略了 that/allowing 192 位密钥而没有错误。
使用 Rfc2898DeriveBytes
to derive your AES key rather than a Base64 transformation of a raw string. RFC 2898 定义 PBKDF2(Password-Based 密钥派生函数 2),一个 HMAC-based 密钥派生函数来安全地从密码派生加密密钥,并提供 HMAC/SHA1与大量迭代一起使用以减轻对您的密钥的 brute-force 攻击。
您只是在 PowerShell 中调用
TransformFinalBlock()
进行加密和解密。我想如果消息长于一个块(16 字节),这将无法加密或解密完整的消息。尝试使用This is a plaintext message.
(29 字节)这样的输入消息。我相信你想同时使用TransformBlock()
和TransformFinalBlock()
.静态 IV 不安全的说法是正确的(违背了 IV 的目的,它应该是唯一的,并且 non-predictable 对于每个使用相同密钥的加密操作)。
AesManaged
已经提供了一种方法GenerateIV()
来生成令人满意的 IV,您可以从 IV 属性 访问它并添加到密文中。您的 Base64 编码密文的 PowerShell 输出为 44 个字符(Base64 中的
16 byte IV + 16 byte ciphered message = 32 bytes -> 44 bytes
)。您的 Python Base64 输出是 24 个字符(Base64 中的16 bytes -> 24 bytes
)。此输出不包括 IV 或消息(或其他不太可能导致输出受限的原因)。查看代码,您的encrypt
方法不会将 IV 添加到密文中。最后,在这一点上,您的代码应该可以工作并且内部一致并且 cross-compatible。在这里您应该重新审视几个设计决策:
零填充是 non-standard,虽然您手动实现了它,但更需要像
中都有丰富的实现和代码示例来实现这一点PKCS #5/#7
这样的 well-defined 填充方案。在 Python 和 .NET.您正在使用CBC block cipher mode of operation。虽然 CBC 可以保密,但它不提供完整性。您应该使用经过身份验证的加密模式 (AE/AEAD),例如 GCM 或 EAX。如果不能,请使用 HMAC 结构(如 HMAC/SHA-256 和 与您的加密密钥不同 共享秘密,并使用 constant-time 方法 [=45= 验证 MAC ]在尝试解密之前。