I made a Python script to encrypt plaintext files using the symmetric-key algorithm described in this video. I then created a second script to decrypt the encrypted message. Here is the original text:
I came, I saw, I conquered.
Here is the text after being encrypted and decrypted:
I came, I saw, I conquerdd.
Almost perfect, except for a single letter. For longer texts, there will be multiple letters which are just off ie the numerical representation of the character which appears is one lower than the numerical representation of the original character. I have no idea why this is.
Here's how my scripts work. First, I generated a random sequence of digits -- my PAD -- and saved it in the text file "pad.txt". I won't show the code because it is so straightforward. I then saved the text which I want to be encrypted in "text.txt". Next, I run the encryption script, which encrypts the text and saves it in the file "encryptedText.txt":
#!/usr/bin/python3.4
import string
def getPad():
padString = open("pad.txt","r").read()
pad = padString.split(" ")
return pad
def encrypt(textToEncrypt,pad):
encryptedText = ""
possibleChars = string.printable[:98] # last two elements are not used bec
# ause they don't show up well on te
# xt files.
for i in range(len(textToEncrypt)):
char = textToEncrypt[i]
if char in possibleChars:
num = possibleChars.index(char)
else:
return False
encryptedNum = num + int(pad[(i)%len(pad)])
if encryptedNum >= len(possibleChars):
encryptedNum = encryptedNum - len(possibleChars)
encryptedChar = possibleChars[encryptedNum]
encryptedText = encryptedText + encryptedChar
return encryptedText
if __name__ == "__main__":
textToEncrypt = open("text.txt","r").read()
pad = getPad()
encryptedText = encrypt(textToEncrypt,pad)
if not encryptedText:
print("""An error occurred during the encryption process. Confirm that \
there are no forbidden symbols in your text.""")
else:
open("encryptedText.txt","w").write(encryptedText)
Finally, I decrypt the text with this script:
#!/usr/bin/python3.4
import string
def getPad():
padString = open("pad.txt","r").read()
pad = padString.split(" ")
return pad
def decrypt(textToDecrypt,pad):
trueText = ""
possibleChars = string.printable[:98]
for i in range(len(textToDecrypt)):
encryptedChar = textToDecrypt[i]
encryptedNum = possibleChars.index(encryptedChar)
trueNum = encryptedNum - int(pad[i%len(pad)])
if trueNum < 0:
trueNum = trueNum + len(possibleChars)
trueChar = possibleChars[trueNum]
trueText = trueText + trueChar
return trueText
if __name__ == "__main__":
pad = getPad()
textToDecrypt = open("encryptedText.txt","r").read()
trueText = decrypt(textToDecrypt,pad)
open("decryptedText.txt","w").write(trueText)
Both scripts seem very straightforward, and they obvious work almost perfectly. However, every once in a while there is an error and I cannot see why.
I found the solution to this problem. It turns out that every character that was not decrypted properly was encrypted to \r, which my text editor changed to a \n for whatever reason. Removing \r from the list of possible characters fixed the issue.
Related
Getting error messages when I am trying to print out comments or submission with emojis in it. How can I just disregard and print only letters and numbers?
Using Praw to webscrape
top_posts2 = page.top(limit = 25)
for post in top_posts2:
outputFile.write(post.title)
outputFile.write(' ')
outputFile.write(str(post.score))
outputFile.write('\n')
outputFile.write(post.selftext)
outputFile.write('\n')
submissions = reddit.submission(id = post.id)
comment_page = submissions.comments
top_comment = comment_page[0] #by default, this will be the best comment of the post
commentBody = top_comment.body
outputFile.write(top_comment.body)
outputFile.write('\n')
I want to output only letters and numbers. and maybe some special characters (or all)
There's a couple ways you can do this. I would recommend creating kind of a "text cleaning" function
def cleanText(text):
new_text = ""
for c in text: # for each character in the text
if c.isalnum(): # check if it is either a letter or number (alphanumeric)
new_text += c
return new_text
or if you want to include specific non-alphanumeric numbers
def cleanText(text):
valid_symbols = "!##$%^&*()" # <-- add whatever symbols you want here
new_text = ""
for c in text: # for each character in the text
if c.isalnum() or c in valid_symbols: # check if alphanumeric or a valid symbol
new_text += c
return new_text
so then in your script you can do something like
commentBody = cleanText(top_comment.body)
i try to encrypt the text with python and then i execute my code i get an error :
import base64
import boto3
from Crypto.Cipher import AES
PAD = lambda s: s + (32 - len(s) % 32) * ' '
def get_arn(aws_data):
return 'arn:aws:kms:{region}:{account_number}:key/{key_id}'.format(**aws_data)
def encrypt_data(aws_data, plaintext_message):
kms_client = boto3.client(
'kms',
region_name=aws_data['region'])
data_key = kms_client.generate_data_key(
KeyId=aws_data['key_id'],
KeySpec='AES_256')
cipher_text_blob = data_key.get('CiphertextBlob')
plaintext_key = data_key.get('Plaintext')
# Note, does not use IV or specify mode... for demo purposes only.
cypher = AES.new(plaintext_key, AES.MODE_ECB)
encrypted_data = base64.b64encode(cypher.encrypt(PAD(plaintext_message)))
# Need to preserve both of these data elements
return encrypted_data, cipher_text_blob
def main():
# Add your account number / region / KMS Key ID here.
aws_data = {
'region': 'eu-west-1',
'account_number': '701177775058',
'key_id': 'd67e033d-83ac-4b5e-93d4-aa6cdc3e292e',
}
# And your super secret message to envelope encrypt...
plaintext = PAD('Hello, World!')
# Store encrypted_data & cipher_text_blob in your persistent storage. You will need them both later.
encrypted_data, cipher_text_blob = encrypt_data(aws_data, plaintext)
print(encrypted_data)
if __name__ == '__main__':
main()
i Get : raise TypeError("Only byte strings can be passed to C code")
TypeError: Only byte strings can be passed to C code
Maybe whom know why? and how can i fix it ? please suggest!
Writing #Jeronimo's comment as an answer here, I was stuck with this same problem too and this helped.
Append a .encode("utf-8") to whatever you are passing to cypher.encrypt() function.
cypher.encrypt(PAD(plaintext_message).encode("utf-8"))
Note: this seems to be for python 3.x. For 2.x this same solution may not work.
I've generated a public and private key with pycrypto, and I save them to a file using export key:
from Crypto.PublicKey import RSA
bits=2048
new_key = RSA.generate(bits, e=65537)
prv = open('keymac.pem','w')
prv.write(new_key.exportKey('PEM'))
prv.close()
pub = open('pubmac.pem', 'w')
pub.write(new_key.publickey().exportKey('PEM'))
pub.close()
I use the public key to encrypt a file (following http://insiderattack.blogspot.com/2014/07/encrypted-file-transfer-utility-in.html#comment-form)
When I read the file to decrypt it, I get "Ciphertext with incorrect length."
I added a try-except block around the decryption code on Deepal Jayasekara example:
try:
encryptedonetimekey = filetodecrypt.read(512)
privatekey = open("keymac.pem", 'r').read()
rsaofprivatekey = RSA.importKey(privatekey)
pkcs1ofprivatekey = PKCS1_OAEP.new(rsaofprivatekey)
aesonetimekey = pkcs1ofprivatekey.decrypt(encryptedonetimekey)
except Exception as decrypprivkeyerr:
print "Decryption of the one time key using the private key failed!!"
print "Key error == %s" %decrypprivkeyerr
raise Exception("Decryption using Private key failed error = %s" %decrypprivkeyerr)
Am I missing something? Should I save the private key differently? Am I not reading the private key correctly?
This doesnt answer your question directly but it may give you some clues to the problem. Im using two functions for encrypting content to a file rather than encrypting a file directly. One for encrypting (in my case username and password) to a file then another to decrypt that data to use as needed.
Note the need for the padding
Creat Encrypted Content In File:
from Crypto.Cipher import AES
import base64
import os
import argparse
parser = argparse.ArgumentParser(description='Arguments used to generate new credentials file, Use: -u for username, -p for password')
parser.add_argument('-u', help='Specify username', required=True)
parser.add_argument('-p', help='Specify password', required=True)
parser.add_argument('-b', help='Specify debug', required=False, action='store_true')
args = vars(parser.parse_args())
def encrypt(username, password):
#Encrypt Credentials To '.creds' file, including 'secret' for username and password
dir_path = os.path.dirname(os.path.realpath(__file__))
# 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
# generate a random secret key
user_secret = os.urandom(BLOCK_SIZE)
pass_secret = os.urandom(BLOCK_SIZE)
# 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)))
# create a cipher object using the random secret
user_cipher = AES.new(user_secret)
pass_cipher = AES.new(pass_secret)
# encode a string
user_encoded = EncodeAES(user_cipher, username)
pass_encoded = EncodeAES(pass_cipher, password)
try:
with open('.creds', 'w') as filename:
filename.write(user_encoded + '\n')
filename.write(user_secret + '\n')
filename.write(pass_encoded + '\n')
filename.write(pass_secret + '\n')
filename.close()
print '\nFile Written To: ', dir_path + '/.creds'
except Exception, e:
print e
if args['b']:
print((user_encoded, user_secret), (pass_encoded, pass_secret))
username = args['u']
password = args['p']
encrypt(username, password)
Decrypt The Data
def decrypt(dir_path, filename):
#Read '.creds' file and return unencrypted credentials (user_decoded, pass_decoded)
lines = [line.rstrip('\n') for line in open(dir_path + filename)]
user_encoded = lines[0]
user_secret = lines[1]
pass_encoded = lines[2]
pass_secret = lines[3]
# 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 = '{'
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
# create a cipher object using the random secret
user_cipher = AES.new(user_secret)
pass_cipher = AES.new(pass_secret)
# decode the encoded string
user_decoded = DecodeAES(user_cipher, user_encoded)
pass_decoded = DecodeAES(pass_cipher, pass_encoded)
return (user_decoded, pass_decoded)
The error message, "Ciphertext with incorrect length", has told us all. That means,
cipher text exceeded the limit length which can be calculated by (length of key,
1024.2048..)/8. to solve this problem, you can separate the cipher text and decrypt
them within a loop, then assemble all the decrypted byte string. My code in Python 3.6 for reference:
# 1024/8
default_length = 128
encrypt_str = str(data["content"])
sign_str = str(data["sign"])
try:
rsa_private_key = RSA.importKey(private_key)
encrypt_byte = base64.b64decode(encrypt_str.encode())
length = len(encrypt_byte)
cipher = PKCS115_Cipher(rsa_private_key)
if length < default_length:
decrypt_byte = cipher.decrypt(encrypt_byte, 'failure')
else:
offset = 0
res = []
while length - offset > 0:
if length - offset > default_length:
res.append(cipher.decrypt(encrypt_byte[offset: offset +
default_length], 'failure'))
else:
res.append(cipher.decrypt(encrypt_byte[offset:], 'failure'))
offset += default_length
decrypt_byte = b''.join(res)
decrypted = decrypt_byte.decode()
I have a neat little script in python that I would like to port to Ruby and I think it's highlighting my noobishness at Ruby. I'm getting the error that there is an unexpected END statement, but I don't see how this can be so. Perhaps there is a keyword that requires an END or something that doesn't want an END that I forgot about. Here is all of the code leading up to the offending line Offending line is commented.
begin
require base64
require base32
rescue LoadError
puts "etext requires base32. use 'gem install --remote base32' and try again"
end
# Get a string from a text file from disk
filename = ARGV.first
textFile = File.open(filename)
text = textFile.read()
mailType = "text only" # set the default mailType
#cut the email up by sections
textList1 = text.split(/\n\n/)
header = textList1[0]
if header.match (/MIME-Version/)
mailType = "MIME"
end
#If mail has no attachments, parse as text-only. This is the class that does this
class TextOnlyMailParser
def initialize(textList)
a = 1
body = ""
header = textList[0]
#parsedEmail = Email.new(header)
while a < textList.count
body += ('\n' + textList[a] + '\n')
a += 1
end
#parsedEmail.body = body
end
end
def separate(text,boundary = nil)
# returns list of strings and lists containing all of the parts of the email
if !boundary #look in the email for "boundary= X"
text.scan(/(?<=boundary=).*/) do |bound|
textList = recursiveSplit(text,bound)
end
return textList
end
if boundary
textList = recursiveSplit(text,boundary)
end
end
def recursiveSplit(chunk,boundary)
if chunk.is_a? String
searchString = "--" + boundary
ar = cunk.split(searchString)
return ar
elsif chunk.is_a? Array
chunk do |bit|
recursiveSplit(bit,boundary);
end
end
end
class MIMEParser
def initialize(textList)
#textList = textList
#nestedItems = []
newItem = NestItem.new(self)
newItem.value = #textList[0]
newItem.contentType = "Header"
#nestedItems.push(newItem)
#setup parsed email
#parsedEmail = Email.new(newItem.value)
self._constructNest
end
def checkForContentSpecial(item)
match = item.value.match (/Content-Disposition: attachment/)
if match
filename = item.value.match (/(?<=filename=").+(?=")/)
encoding = item.value.match (/(?<=Content-Transfer-Encoding: ).+/)
data = item.value.match (/(?<=\n\n).*(?=(\n--)|(--))/m)
dataGroup = data.split(/\n/)
dataString = ''
i = 0
while i < dataGroup.count
dataString += dataGroup[i]
i ++
end #<-----THIS IS THE OFFENDING LINE
#parsedEmail.attachments.push(Attachment.new(filename,encoding,dataString))
end
Your issue is the i ++ line, Ruby does not have a post or pre increment/decrement operators and the line is failing to parse. I can't personally account as to why i++ evaluates in IRB but i ++ does not perform any action.
Instead replace your ++ operators with += 1 making that last while:
while i < dataGroup.count
dataString += dataGroup[i]
i += 1
end
But also think about the ruby way, if you're just adding that to a string why not do a dataString = dataGroup.join instead of looping over with a while construct?
So I wrote a little script in python that brings up a gui with 2 buttons and a text field. you type into the text area and click encrypt and the text all of a sudden looks crazy. coooooool.
but then when you hit decrypt, it doesn't go back to the original. here is the code
from Tkinter import *
import ttk
root = Tk()
textArea = Text(root, state = "normal")
def encrypt():
message = textArea.get('1.0', 'end')
newMessage = ''
lastChar = ''
for c in message:
if lastChar != '':
newMessage += chr((ord(c) + ord(lastChar)) % 256)
lastChar = chr((ord(c) + ord(lastChar)) % 256)
else:
newMessage += chr((ord(c) + 5) % 256)
lastChar = chr((ord(c) + 5) % 256)
textArea.delete('1.0', 'end')
textArea.insert('end', newMessage)
def decrypt():
message = textArea.get('1.0', 'end')
newMessage = ''
lastChar = ''
for c in message:
if lastChar != '':
newMessage += chr((ord(c) - ord(lastChar)) % 256)
lastChar = chr((ord(c) - ord(lastChar)) % 256)
else:
newMessage += chr((ord(c) - 5) % 256)
lastChar = chr((ord(c) - 5) % 256)
textArea.delete('1.0', 'end')
textArea.insert('end', newMessage)
encrypt = ttk.Button(root, text = "Encrypt", command = encrypt)
encrypt.pack({"side": "top"})
decrypt = ttk.Button(root, text = "Decrypt", command = decrypt)
decrypt.pack({"side": "top"})
textArea.pack({"side": "bottom"});
mainloop()
the problem is that it doesn't show the original text. It just seems to make it more cryptic. what is wrong here? Please help.
update:
a changed it so it just adds 5. and It works. So that tells me that it's the part where I add last characters code value. There is still one problem: it adds a new line and this strange line character (not the pipe ---> |).
now the code is fixed. thanks. here is the result:
WW1ueCVueCV1d2p5eX4laHR0cTMlWW1mc3AlfnR6JXh5ZmhwJXR7andrcXR8JWt0dyV5bWolZnN4fGp3Mw8=
after I made the decrypt so It doesn't take away five, it worked. Thanks again.
You can't put arbitrary bytes into a text area.
Your encryption algorithm is purely bytewise - it works on the numeric values of the characters. This isn't going to work, even for ASCII, because byte 0 is an ASCII NUL - treated specially, and byte 127 (what 'Z', character 122, becomes when shifted up by five) is a DEL character: not what you want! Moving beyond ASCII, the real world's UTF-8 has thousands of invalid byte sequences.
If you're going to be using bytewise encryption like this, you can't put the results into a text field - it's binary data. If you want it to be able to go into a text field, you must make sure it's valid text without control characters, NUL bytes, or invalid byte sequences.
One easy way of making the binary sanitary is to Base64-encode it on encryption, and have the first step of decryption be Base64 decoding.
Final note: you're also encrypting the newline at the end of the text area. Probably not something you wish to do.
This seems a rather trivial "encryption" to break. Everyone who has the source can read your messages. A secure cryptosystem should not depend on the algorithm being secure.
You could use the Python Cryptography Toolkit (www.dlitz.net/software/pycrypto/). It has a host of different encryption algorithms available.
You can find some examples of how to use it at: http://www.laurentluce.com/posts/python-and-cryptography-with-pycrypto/
If you don't care about security, why not use e.g. one of the standard encodings (see ยง7.8 'codec' of the standard library reference);
The Ceasar cipher:
>>> res = 'this is a test'.encode('rot_13')
>>> print res
guvf vf n grfg
>>> print res.decode('rot_13')
this is a test
Base64 encoding:
>>> res = 'this is a test'.encode('base64')
>>> print res
dGhpcyBpcyBhIHRlc3Q=
>>> print res.decode('base64')
this is a test