Trying to solve Cryptopals Challenge 10 where have to CBC decrypt a text file against "YELLOW SUBMARINE" with an IV of all ASCII 0 (\x00\x00\x00 &c).
Link to the text file is following:
http://cryptopals.com/static/challenge-data/10.txt
I have followed the algorithm CBC uses by taking cipher text, decrypting(using ECB decryption) and then taking xor with Initialization Vector for first block and ciphertext(i-1) for subsequent blocks. However for some not-understandable reason I am not getting a readable decryption. I just see some weird characters when I print after decryption:
from Crypto.Cipher import AES
key ='YELLOW SUBMARINE'
iv = "%00%00%00"*32
iv = iv.replace('%',r'\x')
#XOR-ing function
def xor_strings(a, b):
return "".join(chr(ord(a1) ^ ord(b1)) for a1, b1 in zip(a, b))
#Taking input file and converting it into a single string
file = open('10.txt','r')
data = file.read()
block = 128
obj = AES.new(key, AES.MODE_ECB)
def split_len(string, size):
return [string[i:i+size] for i in range(0, len(string), size)]
mylist = split_len(data,block)
decrypted = ""
for i in range (0,len(mylist)):
mystr = obj.decrypt(mylist[i])
if (i==0):
decrypted = decrypted + xor_strings(mystr,iv)
else:
decrypted = decrypted + xor_strings(mystr, mylist[i-1])
print decrypted
What might be the problem here ?
The iv needs to be 16 zero bytes (the question is not clearly worded here when it says “ASCII 0”):
iv = "\x00" * 16
You need to base64 decode the file before decrypting it:
from base64 import b64decode
#...
file = open('10.txt','r')
data = file.read()
data = b64decode(data)
Finally your block size needs to be in bytes for this code to work, not bits:
block = 16
Related
I'm writing a test script which I'm using to try and decrypt an encrypted string I have the key to. However, while the code somewhat runs, it is not printing the full string/value that I am expecting (and know the result for).
For example, rather than returning ThisIsTheStringThatWorks it is returning atWorks
Here is the code:
import base64
import hashlib
from Crypto.Cipher import AES
BLOCK_SIZE = 16
unpad = lambda s : s[0:-s[-1]]
def decrypt(enc, secret_key):
private_key = hashlib.sha256(secret_key.encode('utf-8')).digest()
enc = base64.b64decode(enc)
iv = enc[:BLOCK_SIZE]
cipher = AES.new(private_key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(enc[BLOCK_SIZE:]))
decrypted = decrypt(mail_pass, secret_key)
print(bytes.decode(decrypted))
Any help would be greatly appreciated. Thanks in advance.
You have not included the IV in the encrypted message. You are using the first encrypted block as the IV during decryption and as a result the data encrypted in the first block is lost.
You must concatenate the IV and the cipher text in the encryption routine if you want this decryption routine to work:
enc = iv + cipher.encrypt(clear)
I have encrypted some of the data. Here my encryption converter is AES-256-CBC.
import base64
import hashlib
from Crypto import Random
from Crypto.Cipher import AES
class AESCipher(object):
def __init__(self, key):
self.key = hashlib.sha256(key.encode()).digest()
def encrypt(self, raw):
raw = self._pad(raw)
iv = Random.new().read(AES.block_size)
cipher = AES.new(self.key, AES.MODE_CBC, iv)
return base64.b64encode(iv + cipher.encrypt(raw))
def _pad(self, s):
return s + (AES.block_size- len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
key = some key
raw='한글 (한국말)이라고도하는 한국어는 남한과 한국의 공식 언어입니다'
AESCipher_obj=AESCipher(key)
val = AESCipher_obj.encrypt(raw)
print(val)
When I execute my code it gives me that error.
> Traceback (most recent call last): File "aes2.py", line 27, in
> <module>
> s= AESCipher_obj.encrypt(raw) File "aes2.py", line 17, in encrypt
> return base64.b64encode(iv + cipher.encrypt(raw)) File "/usr/local/lib/python3.5/dist-packages/Crypto/Cipher/blockalgo.py",
> line 244, in encrypt
> return self._cipher.encrypt(plaintext) ValueError: Input strings must be a multiple of 16 in lengt
My input data is in the Korean language but When I try with the English language it is working perfectly. So how to encrypt for a specific language?
Assuming that you use a Python3 version, your string is an unicode string. You should encode it to an utf8 encoded byte string before padding it because the length will change. It works for ascii strings, because the length of the encoded byte string is the length of the unicode string.
So in your code, you must simply do:
key = some key
raw ='한글 (한국말)이라고도하는 한국어는 남한과 한국의 공식 언어입니다'
byte_raw = raw.encode('utf8')
AESCipher_obj=AESCipher(key)
val = AESCipher_obj.encrypt(byte_raw)
print(val)
After decrypting, you will get a byte string that should be decoded with .decode('utf8') to convert it to a Python3 (unicode) string.
Your Error:
ValueError: Input strings must be a multiple of 16 in length
AES is a block cipher, it works on 16-byte (128-bit) blocks.
The data needs to be padded until they're 16 bytes.
See this answer.
Hope this helps you!
try to encode() your string into byte string maybe ? and then padding it.
you can see here https://github.com/dlitz/pycrypto/blob/master/lib/Crypto/Cipher/blockalgo.py
that it's working with bytestring instead of unicode (you are using python3)
your step are correct, and I see nothing wrong with the padding mechanism
how do I use this pycrypto code in pycryptodome:
#!/usr/bin/env python
from Crypto.Cipher import AES
import base64
import os
# the block size for the cipher object; must be 16 per FIPS-197
BLOCK_SIZE = 16
# the character used for padding--with a block cipher such as AES, the value
# you encrypt must be a multiple of BLOCK_SIZE in length. This character is
# used to ensure that your value is always a multiple of BLOCK_SIZE
PADDING = '{'
# one-liner to sufficiently pad the text to be encrypted
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
# one-liners to encrypt/encode and decrypt/decode a string
# encrypt with AES, encode with base64
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
# generate a random secret key
secret = os.urandom(BLOCK_SIZE)
# create a cipher object using the random secret
cipher = AES.new(secret)
# encode a string
encoded = EncodeAES(cipher, 'password')
print 'Encrypted string:', encoded
# decode the encoded string
decoded = DecodeAES(cipher, encoded)
print 'Decrypted string:', decoded
At the very least, you need to replace:
cipher = AES.new(secret)
with:
cipher = AES.new(secret, AES.MODE_ECB)
PyCryptodome is almost compatible with PyCrypto, with the exceptions of a few dangeours APIs (like leaving ECB as default encryption mode).
My goal is to be able to AES encrypt a string in PowerShell, send it to a UNIX system with python available, and decrypt the string back to plain text. I would also like to be able to do the inverse. I am not a crypto guy or a PowerShell/python programmer, but this is what I have been able to do with the code so far:
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"
The PowerShell script seems to be working fine for encrypting and decrypting. For the python side, I can define the same AES key value since it is just base64 encoded of my key's passphrase. However, I do not get the same encrypted value of the string when executing (e.g. PowerShell outputs UXKWIhtaUgFOvN13bvA4tx4+2Hjkv4v6I1G3Xfl6zp0= and Python outputs BOJ3Ox4fJxR+jFZ0CBQ25Q==). I believe these would need to match in order to be able to decrypt but I could be mistaken. I know setting a static IV and key makes it insecure, but I am willing to do that in order to be able to encrypt and decrypt across platforms (unless there is a better method using AES). Any help would be appreciated.
Python code
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()
A couple suggestions:
A 16 character ASCII string is 128^16 = 5.19229686e33 possible key inputs. Base64-encoding 16 bytes yields 24 bytes (4*ceil(16/3)). So even though you are using a 192 bit AES key (theoretically 6.27710174e57 key combinations), you can use only 1/1208925820422879877545683 [one over one trillion trillion] of them. In fact, you set the key size to be 256 bits, and apparently the code is ignoring that/allowing the 192 bit key without an error.
Use Rfc2898DeriveBytes to derive your AES key rather than a Base64 transformation of a raw string. RFC 2898 defines PBKDF2 (Password-Based Key Derivation Function 2), an HMAC-based Key Derivation Function to securely derive encryption keys from passwords, and provides for HMAC/SHA1 used with a high number of iterations to mitigate brute-force attacks against your key.
You are only invoking TransformFinalBlock() on encrypt and decrypt in PowerShell. I imagine this will fail to encrypt or decrypt the complete message if the message is longer than one block (16 bytes). Try this with an input message like This is a plaintext message. (29 bytes). I believe you want to use both TransformBlock() and TransformFinalBlock().
You are correct that a static IV is unsafe (defeats the purpose of an IV, which should be unique and non-predictable for every encryption operation with the same key). AesManaged already provides a method GenerateIV() to generate a satisfactory IV, which you can access from the IV property and prepend to the cipher text.
Your PowerShell output of the Base64-encoded cipher text is 44 characters (16 byte IV + 16 byte ciphered message = 32 bytes -> 44 bytes in Base64). Your Python Base64 output is 24 characters (16 bytes -> 24 bytes in Base64). Either this output is not including the IV or the message (or some other less likely reason for the limited output). Looking at the code, your encrypt method does not prepend the IV onto the cipher text.
Finally, at this point, your code should work and be both internally consistent and cross-compatible. Here you should revisit a couple design decisions:
Zero padding is non-standard and while you have implemented it manually, a well-defined padding scheme like PKCS #5/#7 is more desirable. There are bountiful implementations and code examples for implementing this in both Python and .NET.
You are using the CBC block cipher mode of operation. While CBC is fine for confidentiality, it does not provide integrity. You should use an authenticated encryption mode (AE/AEAD) like GCM or EAX. If you cannot, provide a message authentication code (MAC) over the cipher text by using an HMAC construction like HMAC/SHA-256 with a different shared secret than your encryption key and verify the MAC with a constant-time method before attempting decryption.
I am trying to encrypt a large file with public key itself(It is the teacher's requirement, can't use AES keys), even though I know I can only encrypt 128 bytes once at a time with a 1024 bit key. I am splitting the file(any type, .txt.pdf.xls.exe) into 128 byte chunks and encrypt each chunk with my public key.
from Crypto.PublicKey import RSA
from Crypto import Random
import base64
def splitter(content,n):
return [content[i:i+n] for i in range(0, len(content), n)]
####################
def encryption(public_key, content):
list = splitter(content,128)
enc_data = ''
for i in list:
enc_data = enc_data+base64.encodestring(public_key.encrypt(i, None)[0])
#print len(base64.encodestring(public_key.encrypt(i, None)[0]))
print base64.encodestring(public_key.encrypt(i, None)[0])
return enc_data
####################
def decryption(private_key, content):
list = splitter(content,175)
dec_data = ''
for i in list:
dec_data = dec_data+private_key.decrypt(base64.decodestring(i))
return dec_data
####################
public_key=RSA.importKey(open('public_key.pem','r').read())
private_key=RSA.importKey(open('private_key.pem','r').read())
f = open('C:\Users\Administrator\Desktop\excel_example.xls','rb').read()
enc = encryption(public_key, f)
print enc
dec = decryption(private_key, enc)
print dec
usually, every 128 byte chunk will be converted into a 175 bits base64 code, except some 128 byte chunks became "AA=="
KgVqcHadyuHff0EjRC2sq83VbM8joyAp99TKYHjJQJL+l4WZd4rDnC1y/Xd7Vif60gK7Mz3h+8it
iDs4ZDD2chQz4IU0CznoeYUa5o7nl/uwiFppbXx1AlGhRO+L3Olz32eIph2oJlHvmshfMmysnXpJ
zDAqAkOOYBcagonY/7s=
QpHGS7x5bTde8YqifMIOonvUjigjpktYONSDWJU0vSIuODCiG1GPJsum4pOyJ2BseCKzTD8qGMoU
rfbHFBAObjOlkU3RjxCLuOrCk2lSPXC3eNn5DIQqXFtHFX0jfkj/hnrl0R5nQ7R5tmSFTJf2SB5A
c90pAL9hcBP8eEvnAYM=
AA==
AA==
AA==
AA==
AA==
AA==
so far, this code is only working with .txt file, it can encrypt .txt in any size, but all other file, I have this "AA==" issue.
Any suggestion?
You're using plain or textbook RSA. RSA works only on inputs that are smaller than modulus N which is 1024-bit in your case. So there are bound to be inputs that are 128 byte, but numerically bigger than N which fails to encrypt.
Use for example 127 byte chunks or 64 byte chunks with RSA padding such as PKCS#1 v1.5 or PKCS#1 v2 OAEP.
As a side note: Your scheme is basically RSA in ECB mode which is not semantically secure. It is also easy to see that reordering the blocks will still produce a plaintext when decrypted, but it will be wrong plaintext. You will have to add something like an HMAC over the complete resulting ciphertext to detect reorderings.