This page looks best with JavaScript enabled

Hiding encrypted blobs

 ·  ☕ 9 min read

Intro

Recently I was having a little trouble implementing encryption due to chunk size, and then getting proper padding so I figured I would share a simplified example using Python. Bonus steganography included because why not?


Explanation and Definitions

Encryption: The process of encoding a message so that it can be read only by the sender and the intended recipient. Encryption systems often use two keys, a public key, available to anyone, and a private key that allows only the recipient to decode the message.

In this example we will be using Public Key cryptography based off a generate 2048 bit RSA key, that is then wrapped in a PKCS1_OAEP cipher that is hashed with SHA-2, specifically SHA-256. Simply put RSA isnt considered entirely secure and neither is SHA1, so we are wrapping our RSA in PKCS1_OAEP and modifying the hash that PKCS1_OAEP will be using from SHA-1 to SHA-2 standards. Additionally I couldve taken things a step further and encrypted our key with DES using a passphrase however for demonstrative purposes the way we have things now shall suffice.

Since I only have a limited understanding of cryptography Ill be the first to say my implementation may not be perfect or the strongest out there but for basic purposes it should get the job done. I had immense difficulty finding resources or sample code on encrypting large strings using RSA + PKCS1_OAEP + SHA256 from PyCrypto so it felt good to finally get something working.

  • Steganography - the practice of concealing messages or information within other nonsecret text or data.
  • RSA- "is the most widespread and used public key algorithm. Its security is based on the difficulty of factoring large integers. The algorithm has withstood attacks for 30 years, and it is therefore considered reasonably secure for new designs." Source by dlitz.net, PyCrypto documentation
  • PKCS1_OAEP - Public Key Cryoptography Standard 1 - Optional Asymmetric Encryption Padding
  • SHA - Secure Hash Algorithm. A cryptographic hash function that is a mathmatical operation ran on digital data.

Requirements

In order to get our program running we will first need the help of two libraries. The two libraries that I had to install using pip were Stepic and Pycrypto. Stepic is what we will use for our steganography, and pycrypto will handle our encryption implementation

Generating our Keys

We are going to go ahead and generate our public and private RSA keys to be used for this project. Note its probably not best practice to dump them into a txt file lest someone happen upon them but for learning purposes it should be fine.

2048 is considered secure by the documentation, and PEM will be our output format for readability purposes. This simple script dumps the keys into seperate text files so we can call them later on. Really it should be fairly self explanatory.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/usr/bin/env python 
from Crypto.PublicKey import RSA
  
key = RSA.generate(2048, e=65537)
pub = key.publickey().exportKey("PEM")
priv = key.exportKey("PEM")
  
target = open("pubkey.txt", "w")
target.write(pub)
target.close()
target = open("privkey.txt", "w")
target.write(priv)
target.close()

Encrypting our data

Heres where we really get to the important part, trying to implement some encryption. Now lets explain how we are going to do this.

First we create a new SHA256 hash; then we read in our pubkey.txt as key, next we import the public key using the RSA library, finally we wrap the RSA with the cipher PKCS1_OAEP and the SHA256 hash. The reason we do all this is because RSA by itself is not considered secure so we want to use PKCS1_OAEP to apply a cipher and pad it. The purpose of the SHA256 hash is that by default the pycrypto implementation of PKCS1_OAEP uses SHA1 as its hash, which is also considered insecure. Also note we could use our private key but lets not since we wont be sharing that one. Simply put, not only do we want encryption but we also want padding and a strong hash. Keep in mind, we can of course always implement strong encryption using bigger keys or a salted hash.

Then to create a dummy string I have declared a large block of As, Bs, Cs, and Ds and concatenated them together for a total length of 3000. Now if we were using just RSA we would be encrypting at a chunk size of 256 as thats our RSA modulus of 2048, however since we are using PKCS1_OAEP as our wrapper our chunk size is different. The documentation lays it out pretty simple, Chunk Size = (RSA modulus - 2) -(2 * (hash digest size)). A quick check for the hash digest size of SHA256 in the documentation yields 32 and in the end our chunk size will be 190. So we use our splitter function to break the string into chunks of size 190, then we encrypt those chunks.

Following this to reduce length and also obscure the underlying encryption we use the zlib library and base64 libraries to apply compression and encoding. So zlib.compress to compress our string and base64.b64encode to encode it. We dont need to do this but if youre going about this for your own use you may find it useful in making it a bit harder to discern how you went about implementing your encryption. Fantastic so at this point were ready to take our string and send it to whomever has the private key, but why stop there.

During all my research I saw a multitude of articles also discussing steganography, the act of hiding information in plain sight. So as an added bonus lets take our encryption a step further and throw it into a cat picture. Specifically this cat picture, I used a PNG as it is a lossless format and will retain our information. Stepic makes shoving our information trivial and you can find the annotated lines towards the end of the code.

Now that we have got a basic understanding of what we are implementing and why, heres the code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/python

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
import base64
import zlib
import Image
import stepic


#Function to split key into chunks 
def splitter(string, chunksize):
   return [string[i:i+chunksize] for i in range(0, len(string), chunksize)]

def encrypter():

   target = open('pubkey.txt', 'r')
   key = target.read() #read in PEM formatted key from file 
   target.close()

   #Create new SHA256 hash
   h = SHA256.new()  
   #RSA 2048 bit wrapped with PKCS1 as recommended by documentation
   # Default SHA1 Hash is weak, replacing with SHA2 hash
   rsakey = RSA.importKey(key)
   rsakey = PKCS1_OAEP.new(rsakey, h)
   
   #Construct a large string to practice encrypting
   As = 'A' * 750
   Bs = 'B' * 750
   Cs = 'C' * 750
   Ds = 'D' * 750
   string = As + Bs + Cs + Ds 
   
   #Chunk size = (RSA modulus -2) -( 2 * (hash digest size))
   #In this case (256 -2 ) -( 2 * (32) )= 190
   chunks = splitter(string, 190)
   
   encrypted = '' 

   for chunk in chunks:
      encrypted += rsakey.encrypt(chunk)

   #Lets try compressing the encrypted string a little
   encrypted = zlib.compress(encrypted)

   #Add some b64 encoding to sort of obfuscate our encryption
   encrypted = base64.b64encode(encrypted)
   target = open('testrun.txt', 'w')
   output = target.write(encrypted)
   target.close()
   
   print 'Encrypted String is of size:' + str(len(encrypted)) + '\n'  

   #Steganography, Hiding the encrypted text in plain sight using least significant bit
   #Image has to be png as it is lossless format. 
   im = Image.open("cat.png")
   im2 = stepic.encode(im, encrypted)
   im2.save('encodedcat.png')      
   
def main():
   encrypter()

if __name__ == '__main__':
   main()

This is before we add our encryption, its a large 20.5mb picture so its a bit unwieldy for most regular uses. Sorry if the massive cat picture makes your browser lag a bit.

Cat picture before encryption

Heres our final product. As our encryption came out at about 5747, the cat picture is now of size 21.1mb. This is achieved with a hardly noticable difference in our picture.

Cat picture after encryption

This picture no longer carries the encoding

Decrypting our payload

Since weve laid out all of our groundwork for generating keys and encrypting, we ought to be able to fly through decrypting. Were going to open the picture using the stepic library again and decode it. Then we undo our base64 encoding and zlib compression. After that we split our string into chunks of 256, its not 190 I know but thats what the documentation says the PKCS1_OAEP.decrypt function takes for size and it works. After that we print our decrypted text and the length of it to verify we got everything back. I will drop the annotated code and output below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/python

from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Hash import SHA256
import base64
import zlib
import Image
import stepic

#Split string into chunks 
def splitter(string, chunksize):
   return [string[i:i+chunksize] for i in range(0, len(string), chunksize)]
   
def decryptor():
   
   target = open('privkey.txt', 'r')
   key = target.read()
   target.close()
   
   #Create new SHA256 Hash
   h = SHA256.new()
   
   #RSA 2048 wrapped with PKCS1 as recommended by documentation 
   rsakey = RSA.importKey(key)
   rsakey = PKCS1_OAEP.new(rsakey , h)

   # target = open('testrun.txt' , 'rb') - For reading encrypted string from file
  
   #Open and decode Image to extract encrypted String 
   im = Image.open('encodedcat.png')
   encoded = stepic.decode(im)
   encoded = encoded.decode()
   # encoded = target.read()
   # target.close()
   
   #Undo B64 encoding on top of string
   encrypted = base64.b64decode(encoded)
   
   #Decompress string
   encrypted = zlib.decompress(encrypted)
   decrypted = ''
   #Chunks must be RSA modulus size
   chunks = splitter(encrypted, 256)   
   
   for chunk in chunks: 
      decrypted += rsakey.decrypt(chunk)   
   
   print decrypted
   print len(decrypted)

def main():
   decryptor()
   
if __name__ == '__main__':
   main()

If we run this program on our cat picture our final output comes out as:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD

3000
Share on

Anthony Laiuppa
WRITTEN BY
Anthony Laiuppa
DevSecOps Engineer