Simple way to encode a string according to a password?
Asked Answered
T

22

194

Does Python have a built-in, simple way of encoding/decoding strings using a password?

Something like this:

>>> encode('John Doe', password = 'mypass')
'sjkl28cn2sx0'
>>> decode('sjkl28cn2sx0', password = 'mypass')
'John Doe'

So the string "John Doe" gets encrypted as 'sjkl28cn2sx0'. To get the original string, I would "unlock" that string with the key 'mypass', which is a password in my source code. I'd like this to be the way I can encrypt/decrypt a Word document with a password.

I would like to use these encrypted strings as URL parameters. My goal is obfuscation, not strong security; nothing mission critical is being encoded. I realize I could use a database table to store keys and values, but am trying to be minimalist.

Telegony answered 22/3, 2010 at 6:23 Comment(6)
The term "password" here is inappropriate. You're using this as a cryptographic KEY and you should use that terminology to avoid confusion in your questions as well as any docs, comments, specs, test plans, etc.Pannier
"I'd like this to be the way I can encrypt/decrypt a Word document with a password.", Word already has a built in option to encrypt your documents if you just need to encrypt word documents.Pulmonary
Interestingly, according to this research paper on password storage pitfalls like this, developers who use Stack Overflow tend to produce less secure code. Gee, I wonder why?Doublestop
Also, one should read this answer from security.SEHamachi
One does not simply implement encoding/decoding simplySheelah
found an easy answer someone posted on my similar question: https://mcmap.net/q/36459/-i-need-to-store-passwords-in-config-encrypted-as-password-duplicateEquanimity
S
85

Assuming you are only looking for simple obfuscation that will obscure things from the very casual observer, and you aren't looking to use third party libraries. I'd recommend something like the Vigenere cipher. It is one of the strongest of the simple ancient ciphers.

Vigenère cipher

It's quick and easy to implement. Something like:

import base64

def encode(key, string):
    encoded_chars = []
    for i in xrange(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) + ord(key_c) % 256)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    return base64.urlsafe_b64encode(encoded_string)

Decode is pretty much the same, except you subtract the key.

It is much harder to break if the strings you are encoding are short, and/or if it is hard to guess the length of the passphrase used.

If you are looking for something cryptographic, PyCrypto is probably your best bet, though previous answers overlook some details: ECB mode in PyCrypto requires your message to be a multiple of 16 characters in length. So, you must pad. Also, if you want to use them as URL parameters, use base64.urlsafe_b64_encode(), rather than the standard one. This replaces a few of the characters in the base64 alphabet with URL-safe characters (as it's name suggests).

However, you should be ABSOLUTELY certain that this very thin layer of obfuscation suffices for your needs before using this. The Wikipedia article I linked to provides detailed instructions for breaking the cipher, so anyone with a moderate amount of determination could easily break it.

Sapper answered 22/3, 2010 at 8:11 Comment(10)
The Vignere method looks like a great fit for my needs. Pointing out how to make the hashes URL-safe is the cherry on top. Perfect.Telegony
local variable 'encoded_c' referenced before assignment. I don't really understand the code yet so I can't really fix it myself, sadly.Emanuel
I fixed smehmood's script, and added the decoding function gist.github.com/ilogik/6f9431e4588015ecb194Furniture
Attention! smehmood's code and Adrian Mester's fix both only work for strings with characters from the lower ascii range! See precedence of % operator, unicode input etc. See qneill's answer for working codeExtraordinary
That looks pretty good. The vigenere cipher is perhaps the best classical cipher. While a computer could definitely use babbage's method to break it, this is actually surprisingly difficult to break if you have a short string to encode.Marvelmarvella
@Sapper I'm getting the following error 'str' object cannot be interpreted as an integerKorey
"for i in xrange(string)" might need to chang to "for i in xrange(len(string))"Jiles
encode('mykey', 'é_çèà&é') doesn't work : ValueError: chr() arg not in range(256). @qneill's version works for that.Deegan
encoder and decoder for python 2 and 3: gist.github.com/gowhari/fea9c559f08a310e5cfd62978bc86a1aFrazee
Put this for encoding: encoded_c = chr((ord(string[i]) + ord(key_c)) % 256) and Put this for decoding: encoded_c = chr((ord(string[i]) - ord(key_c)) % 256)Presentational
S
225

Python has no built-in encryption schemes, no. You also should take encrypted data storage serious; trivial encryption schemes that one developer understands to be insecure and a toy scheme may well be mistaken for a secure scheme by a less experienced developer. If you encrypt, encrypt properly.

You don’t need to do much work to implement a proper encryption scheme however. First of all, don’t re-invent the cryptography wheel, use a trusted cryptography library to handle this for you. For Python 3, that trusted library is cryptography.

I also recommend that encryption and decryption applies to bytes; encode text messages to bytes first; stringvalue.encode() encodes to UTF8, easily reverted again using bytesvalue.decode().

Last but not least, when encrypting and decrypting, we talk about keys, not passwords. A key should not be human memorable, it is something you store in a secret location but machine readable, whereas a password often can be human-readable and memorised. You can derive a key from a password, with a little care.

But for a web application or process running in a cluster without human attention to keep running it, you want to use a key. Passwords are for when only an end-user needs access to the specific information. Even then, you usually secure the application with a password, then exchange encrypted information using a key, perhaps one attached to the user account.

Symmetric key encryption

Fernet – AES CBC + HMAC, strongly recommended

The cryptography library includes the Fernet recipe, a best-practices recipe for using cryptography. Fernet is an open standard, with ready implementations in a wide range of programming languages and it packages AES CBC encryption for you with version information, a timestamp and an HMAC signature to prevent message tampering.

Fernet makes it very easy to encrypt and decrypt messages and keep you secure. It is the ideal method for encrypting data with a secret.

I recommend you use Fernet.generate_key() to generate a secure key. You can use a password too (next section), but a full 32-byte secret key (16 bytes to encrypt with, plus another 16 for the signature) is going to be more secure than most passwords you could think of.

The key that Fernet generates is a bytes object with URL- and file-safe base64 characters, so printable:

from cryptography.fernet import Fernet

key = Fernet.generate_key()  # store in a secure location
# PRINTING FOR DEMO PURPOSES ONLY, don't do this in production code
print("Key:", key.decode())

To encrypt or decrypt messages, create a Fernet() instance with the given key, and call the Fernet.encrypt() or Fernet.decrypt(), both the plaintext message to encrypt and the encrypted token are bytes objects.

encrypt() and decrypt() functions would look like:

from cryptography.fernet import Fernet

def encrypt(message: bytes, key: bytes) -> bytes:
    return Fernet(key).encrypt(message)

def decrypt(token: bytes, key: bytes) -> bytes:
    return Fernet(key).decrypt(token)

Demo:

>>> key = Fernet.generate_key()
>>> print(key.decode())
GZWKEhHGNopxRdOHS4H4IyKhLQ8lwnyU7vRLrM3sebY=
>>> message = 'John Doe'
>>> token = encrypt(message.encode(), key)
>>> print(token)
'gAAAAABciT3pFbbSihD_HZBZ8kqfAj94UhknamBuirZWKivWOukgKQ03qE2mcuvpuwCSuZ-X_Xkud0uWQLZ5e-aOwLC0Ccnepg=='
>>> decrypt(token, key).decode()
'John Doe'

Fernet with password – key derived from password, weakens the security somewhat

You can use a password instead of a secret key, provided you use a strong key derivation method. You do then have to include the salt and the HMAC iteration count in the message, so the encrypted value is not Fernet-compatible anymore without first separating salt, count and Fernet token:

import secrets
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

backend = default_backend()
iterations = 100_000

def _derive_key(password: bytes, salt: bytes, iterations: int = iterations) -> bytes:
    """Derive a secret key from a given password and salt"""
    kdf = PBKDF2HMAC(
        algorithm=hashes.SHA256(), length=32, salt=salt,
        iterations=iterations, backend=backend)
    return b64e(kdf.derive(password))

def password_encrypt(message: bytes, password: str, iterations: int = iterations) -> bytes:
    salt = secrets.token_bytes(16)
    key = _derive_key(password.encode(), salt, iterations)
    return b64e(
        b'%b%b%b' % (
            salt,
            iterations.to_bytes(4, 'big'),
            b64d(Fernet(key).encrypt(message)),
        )
    )

def password_decrypt(token: bytes, password: str) -> bytes:
    decoded = b64d(token)
    salt, iter, token = decoded[:16], decoded[16:20], b64e(decoded[20:])
    iterations = int.from_bytes(iter, 'big')
    key = _derive_key(password.encode(), salt, iterations)
    return Fernet(key).decrypt(token)

Demo:

>>> message = 'John Doe'
>>> password = 'mypass'
>>> password_encrypt(message.encode(), password)
b'9Ljs-w8IRM3XT1NDBbSBuQABhqCAAAAAAFyJdhiCPXms2vQHO7o81xZJn5r8_PAtro8Qpw48kdKrq4vt-551BCUbcErb_GyYRz8SVsu8hxTXvvKOn9QdewRGDfwx'
>>> token = _
>>> password_decrypt(token, password).decode()
'John Doe'

Including the salt in the output makes it possible to use a random salt value, which in turn ensures the encrypted output is guaranteed to be fully random regardless of password reuse or message repetition. Including the iteration count ensures that you can adjust for CPU performance increases over time without losing the ability to decrypt older messages.

A password alone can be as safe as a Fernet 32-byte random key, provided you generate a properly random password from a similar size pool. 32 bytes gives you 256 ^ 32 number of keys, so if you use an alphabet of 74 characters (26 upper, 26 lower, 10 digits and 12 possible symbols), then your password should be at least math.ceil(math.log(256 ** 32, 74)) == 42 characters long. However, a well-selected larger number of HMAC iterations can mitigate the lack of entropy somewhat as this makes it much more expensive for an attacker to brute force their way in.

Just know that choosing a shorter but still reasonably secure password won’t cripple this scheme, it just reduces the number of possible values a brute-force attacker would have to search through; make sure to pick a strong enough password for your security requirements.

Alternatives

Obscuring

An alternative is not to encrypt. Don't be tempted to just use a low-security cipher, or a home-spun implementation of, say Vignere. There is no security in these approaches, but may give an inexperienced developer that is given the task to maintain your code in future the illusion of security, which is worse than no security at all.

If all you need is obscurity, just base64 the data; for URL-safe requirements, the base64.urlsafe_b64encode() function is fine. Don't use a password here, just encode and you are done. At most, add some compression (like zlib):

import zlib
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

def obscure(data: bytes) -> bytes:
    return b64e(zlib.compress(data, 9))

def unobscure(obscured: bytes) -> bytes:
    return zlib.decompress(b64d(obscured))

This turns b'Hello world!' into b'eNrzSM3JyVcozy_KSVEEAB0JBF4='.

Integrity only

If all you need is a way to make sure that the data can be trusted to be unaltered after having been sent to an untrusted client and received back, then you want to sign the data, you can use the hmac library for this with SHA1 (still considered secure for HMAC signing) or better:

import hmac
import hashlib

def sign(data: bytes, key: bytes, algorithm=hashlib.sha256) -> bytes:
    assert len(key) >= algorithm().digest_size, (
        "Key must be at least as long as the digest size of the "
        "hashing algorithm"
    )
    return hmac.new(key, data, algorithm).digest()

def verify(signature: bytes, data: bytes, key: bytes, algorithm=hashlib.sha256) -> bytes:
    expected = sign(data, key, algorithm)
    return hmac.compare_digest(expected, signature)

Use this to sign data, then attach the signature with the data and send that to the client. When you receive the data back, split data and signature and verify. I've set the default algorithm to SHA256, so you'll need a 32-byte key:

key = secrets.token_bytes(32)

You may want to look at the itsdangerous library, which packages this all up with serialisation and de-serialisation in various formats.

Using AES-GCM encryption to provide encryption and integrity

Fernet builds on AEC-CBC with a HMAC signature to ensure integrity of the encrypted data; a malicious attacker can't feed your system nonsense data to keep your service busy running in circles with bad input, because the ciphertext is signed.

The Galois / Counter mode block cipher produces ciphertext and a tag to serve the same purpose, so can be used to serve the same purposes. The downside is that unlike Fernet there is no easy-to-use one-size-fits-all recipe to reuse on other platforms. AES-GCM also doesn't use padding, so this encryption ciphertext matches the length of the input message (whereas Fernet / AES-CBC encrypts messages to blocks of fixed length, obscuring the message length somewhat).

AES256-GCM takes the usual 32 byte secret as a key:

key = secrets.token_bytes(32)

then use

import binascii, time
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from cryptography.exceptions import InvalidTag

backend = default_backend()

def aes_gcm_encrypt(message: bytes, key: bytes) -> bytes:
    current_time = int(time.time()).to_bytes(8, 'big')
    algorithm = algorithms.AES(key)
    iv = secrets.token_bytes(algorithm.block_size // 8)
    cipher = Cipher(algorithm, modes.GCM(iv), backend=backend)
    encryptor = cipher.encryptor()
    encryptor.authenticate_additional_data(current_time)
    ciphertext = encryptor.update(message) + encryptor.finalize()        
    return b64e(current_time + iv + ciphertext + encryptor.tag)

def aes_gcm_decrypt(token: bytes, key: bytes, ttl=None) -> bytes:
    algorithm = algorithms.AES(key)
    try:
        data = b64d(token)
    except (TypeError, binascii.Error):
        raise InvalidToken
    timestamp, iv, tag = data[:8], data[8:algorithm.block_size // 8 + 8], data[-16:]
    if ttl is not None:
        current_time = int(time.time())
        time_encrypted, = int.from_bytes(data[:8], 'big')
        if time_encrypted + ttl < current_time or current_time + 60 < time_encrypted:
            # too old or created well before our current time + 1 h to account for clock skew
            raise InvalidToken
    cipher = Cipher(algorithm, modes.GCM(iv, tag), backend=backend)
    decryptor = cipher.decryptor()
    decryptor.authenticate_additional_data(timestamp)
    ciphertext = data[8 + len(iv):-16]
    return decryptor.update(ciphertext) + decryptor.finalize()

I've included a timestamp to support the same time-to-live use-cases that Fernet supports.

Other approaches on this page, in Python 3

AES CFB - like CBC but without the need to pad

This is the approach that All Іѕ Vаиітy follows, albeit incorrectly. This is the cryptography version, but note that I include the IV in the ciphertext, it should not be stored as a global (reusing an IV weakens the security of the key, and storing it as a module global means it'll be re-generated the next Python invocation, rendering all ciphertext undecryptable):

import secrets
from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend

backend = default_backend()

def aes_cfb_encrypt(message, key):
    algorithm = algorithms.AES(key)
    iv = secrets.token_bytes(algorithm.block_size // 8)
    cipher = Cipher(algorithm, modes.CFB(iv), backend=backend)
    encryptor = cipher.encryptor()
    ciphertext = encryptor.update(message) + encryptor.finalize()
    return b64e(iv + ciphertext)

def aes_cfb_decrypt(ciphertext, key):
    iv_ciphertext = b64d(ciphertext)
    algorithm = algorithms.AES(key)
    size = algorithm.block_size // 8
    iv, encrypted = iv_ciphertext[:size], iv_ciphertext[size:]
    cipher = Cipher(algorithm, modes.CFB(iv), backend=backend)
    decryptor = cipher.decryptor()
    return decryptor.update(encrypted) + decryptor.finalize()

This lacks the added armoring of an HMAC signature and there is no timestamp; you’d have to add those yourself.

The above also illustrates how easy it is to combine basic cryptography building blocks incorrectly; All Іѕ Vаиітy‘s incorrect handling of the IV value can lead to a data breach or all encrypted messages being unreadable because the IV is lost. Using Fernet instead protects you from such mistakes.

AES ECB – not secure

If you previously implemented AES ECB encryption and need to still support this in Python 3, you can do so still with cryptography too. The same caveats apply, ECB is not secure enough for real-life applications. Re-implementing that answer for Python 3, adding automatic handling of padding:

from base64 import urlsafe_b64encode as b64e, urlsafe_b64decode as b64d

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend

backend = default_backend()

def aes_ecb_encrypt(message, key):
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    encryptor = cipher.encryptor()
    padder = padding.PKCS7(cipher.algorithm.block_size).padder()
    padded = padder.update(msg_text.encode()) + padder.finalize()
    return b64e(encryptor.update(padded) + encryptor.finalize())

def aes_ecb_decrypt(ciphertext, key):
    cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
    decryptor = cipher.decryptor()
    unpadder = padding.PKCS7(cipher.algorithm.block_size).unpadder()
    padded = decryptor.update(b64d(ciphertext)) + decryptor.finalize()
    return unpadder.update(padded) + unpadder.finalize()

Again, this lacks the HMAC signature, and you shouldn’t use ECB anyway. The above is there merely to illustrate that cryptography can handle the common cryptographic building blocks, even the ones you shouldn’t actually use.

Sacci answered 13/3, 2019 at 16:43 Comment(2)
Thank you very much for such a thorough, detailed and organised answer, I learnt a lot. Thank you very much!Helianthus
A big thank you for the time and effort you put in providing such a complete and detailed answer. Still highly appreciated almost 5 years later.Meanwhile
S
85

Assuming you are only looking for simple obfuscation that will obscure things from the very casual observer, and you aren't looking to use third party libraries. I'd recommend something like the Vigenere cipher. It is one of the strongest of the simple ancient ciphers.

Vigenère cipher

It's quick and easy to implement. Something like:

import base64

def encode(key, string):
    encoded_chars = []
    for i in xrange(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) + ord(key_c) % 256)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    return base64.urlsafe_b64encode(encoded_string)

Decode is pretty much the same, except you subtract the key.

It is much harder to break if the strings you are encoding are short, and/or if it is hard to guess the length of the passphrase used.

If you are looking for something cryptographic, PyCrypto is probably your best bet, though previous answers overlook some details: ECB mode in PyCrypto requires your message to be a multiple of 16 characters in length. So, you must pad. Also, if you want to use them as URL parameters, use base64.urlsafe_b64_encode(), rather than the standard one. This replaces a few of the characters in the base64 alphabet with URL-safe characters (as it's name suggests).

However, you should be ABSOLUTELY certain that this very thin layer of obfuscation suffices for your needs before using this. The Wikipedia article I linked to provides detailed instructions for breaking the cipher, so anyone with a moderate amount of determination could easily break it.

Sapper answered 22/3, 2010 at 8:11 Comment(10)
The Vignere method looks like a great fit for my needs. Pointing out how to make the hashes URL-safe is the cherry on top. Perfect.Telegony
local variable 'encoded_c' referenced before assignment. I don't really understand the code yet so I can't really fix it myself, sadly.Emanuel
I fixed smehmood's script, and added the decoding function gist.github.com/ilogik/6f9431e4588015ecb194Furniture
Attention! smehmood's code and Adrian Mester's fix both only work for strings with characters from the lower ascii range! See precedence of % operator, unicode input etc. See qneill's answer for working codeExtraordinary
That looks pretty good. The vigenere cipher is perhaps the best classical cipher. While a computer could definitely use babbage's method to break it, this is actually surprisingly difficult to break if you have a short string to encode.Marvelmarvella
@Sapper I'm getting the following error 'str' object cannot be interpreted as an integerKorey
"for i in xrange(string)" might need to chang to "for i in xrange(len(string))"Jiles
encode('mykey', 'é_çèà&é') doesn't work : ValueError: chr() arg not in range(256). @qneill's version works for that.Deegan
encoder and decoder for python 2 and 3: gist.github.com/gowhari/fea9c559f08a310e5cfd62978bc86a1aFrazee
Put this for encoding: encoded_c = chr((ord(string[i]) + ord(key_c)) % 256) and Put this for decoding: encoded_c = chr((ord(string[i]) - ord(key_c)) % 256)Presentational
S
76

As you explicitly state that you want obscurity not security, we'll avoid reprimanding you for the weakness of what you suggest :)

So, using PyCrypto:

import base64
from Crypto.Cipher import AES

msg_text = b'test some plain text here'.rjust(32)
secret_key = b'1234567890123456'

cipher = AES.new(secret_key,AES.MODE_ECB) # never use ECB in strong systems obviously
encoded = base64.b64encode(cipher.encrypt(msg_text))
print(encoded)
decoded = cipher.decrypt(base64.b64decode(encoded))
print(decoded)

If someone gets a hold of your database and your code base, they will be able to decode the encrypted data. Keep your secret_key safe!

Schrader answered 22/3, 2010 at 6:34 Comment(12)
I don't think this will work unless msg_text is a multiple of 16 bytes in length, since AES encryption requires blocks that are multiples of 16 in length. A working implementation for msg_text of arbitrary length would need to add padding to the string to get it to a multiple of 16 in length.Replace
@Replace this is true, you have to pack them and secrets must be right-sized too. Padding and then tacking the length on the end would be favourite.Schrader
An example with padding: paste.ubuntu.com/11024555 It works with arbitrary password and message length.Frazee
@Schrader Is it necessary to compute the cipher in this case (AES ECB) always? Just curious, I was thinking of creating once on startup and reusing always on runtime.Shewmaker
@Shewmaker no, this particular encrypt function is stateful dlitz.net/software/pycrypto/api/current/… so you should not try and reuse it.Schrader
@Schrader +1 Thanks for the info and the link. Saved me from a very expensive bug fix in future.Shewmaker
@Schrader Above routine is quite expensive for my requirement. Do you suggest any quicker encryption? For me, primary reason is obfuscating the data and if someone is determined to break it, it's ok, they can cause no harm to the system.Shewmaker
@Shewmaker seed a random number generator with a special 'secret' value. Then xor each byte in the message with a randint(0, 255). This is as insecure as it gets, but its good obfuscation and its fast.Schrader
re - "never use ECB in strong systems obviously": I'm just experimenting with this for my own fun. but I saw the above comment in your code. for those of us with very minimal security/encryption/information-theory background, why the "never use"? maybe needs another question... or maybe there is a link on it.Pigfish
Note: it seems the pycrypto package no longer works at some places. My heroku app failed because of it. This link suggests to use pycryptodome instead: github.com/digitalocean/netbox/issues/1527Latoyialatreece
Regarding @TrevorBoydSmith's question, this question explains why you should avoid ECB in strong systems and clarifies the comment in the answer crypto.stackexchange.com/questions/20941/…Henrie
Note that b"..." is really important; if not it will fail with Python 3 with TypeError: Object type <class 'str'> cannot be passed to C code.Deegan
K
63

Here's a Python 3 version of the functions from @qneill 's answer:

import base64
def encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = chr((ord(clear[i]) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc).encode()).decode()

def decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc).decode()
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

The extra encode/decodes are needed because Python 3 has split strings/byte arrays into two different concepts, and updated their APIs to reflect that..

Kasper answered 6/7, 2016 at 11:55 Comment(4)
Thanks Ryan, fwiw you typo'd @qniellEmbay
For those wondering, the differences are the .encode()).decode(). in the return of encode(), and .decode() in the second line in decode().Unsung
Hmm, the encoded code not really unique, I ran a test and it show every code key of 11,22,33,44,...,88,99,111,222,... always have another same code as before. But I appreciate itFluid
@Ryan Barrett, is it possible to check the correctness of the password when decoding. Let's say I send one an encoded string who know the key, what if he enter the key with a typo? The decode still gives him a "decoded" string, but it is not the right one, how he can tell?Aboral
E
56

The "encoded_c" mentioned in the @smehmood's Vigenere cipher answer should be "key_c".

Here are working encode/decode functions.

import base64
def encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = chr((ord(clear[i]) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc))

def decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

Disclaimer: As implied by the comments, this should not be used to protect data in a real application, unless you read this and don't mind talking with lawyers:

What's wrong with XOR encryption?

Embay answered 1/5, 2013 at 16:7 Comment(5)
Very useful, thanks. I've posted a Python 3 version below (it looked ugly in the comments)Kasper
"Schneier's Law": Anyone, from the most clueless amateur to the best cryptographer, can create an algorithm that he himself can't break. Don't use this, it is not even close to secure.Inhale
Hi @Inhale - There was a bug in one of the other posts, my post was a bug fix, some may find it instructive. I find the caveat from the answer "Assuming you are only looking for simple obfuscation" more apt; "not even close to secure" is a value judgement with unspoken assumptions.Embay
Great! This version works also for strings with accents, whereas @smehmood's version doesn't. Usage example: encodedmsg = encode('mypassword', 'this is the message éçàèç"') print encodedmsg print decode('mypassword', encodedmsg), it works fine.Deegan
@basj Thank you for the exampleHyonhyoscine
N
29

Disclaimer: As mentioned in the comments, this should not be used to protect data in a real application.

What's wrong with XOR encryption?

https://crypto.stackexchange.com/questions/56281/breaking-a-xor-cipher-of-known-key-length

https://github.com/hellman/xortool


As has been mentioned the PyCrypto library contains a suite of ciphers. The XOR "cipher" can be used to do the dirty work if you don't want to do it yourself:

from Crypto.Cipher import XOR
import base64

def encrypt(key, plaintext):
  cipher = XOR.new(key)
  return base64.b64encode(cipher.encrypt(plaintext))

def decrypt(key, ciphertext):
  cipher = XOR.new(key)
  return cipher.decrypt(base64.b64decode(ciphertext))

The cipher works as follows without having to pad the plaintext:

>>> encrypt('notsosecretkey', 'Attack at dawn!')
'LxsAEgwYRQIGRRAKEhdP'

>>> decrypt('notsosecretkey', encrypt('notsosecretkey', 'Attack at dawn!'))
'Attack at dawn!'

Credit to https://mcmap.net/q/36312/-simple-way-to-encode-a-string-according-to-a-password for the base64 encode/decode functions (I'm a python newbie).

Nicobarese answered 13/2, 2014 at 12:43 Comment(4)
note: the Crypto module is installed in python3 by installed pycrptop, not Crypto. sudo pip3 install pycrypto.Latoyialatreece
note: pycrypto failed to install on herokuapp at my end. I found this posting.. seems to tell that pycrypto package has been replaced with another one called pycryptodome insteal, and that XOR method has been deprecated : github.com/digitalocean/netbox/issues/1527Latoyialatreece
Do not ever use this method, note the description of this 'cipher' in the documentation: XOR toy cipher, XOR is one the simplest stream ciphers. Encryption and decryption are performed by XOR-ing data with a keystream made by contatenating the key. Do not use it for real applications!.Sacci
@MartijnPieters you're right. Hopefully my edit has made that point clear.Nicobarese
E
13

Here's an implementation of URL Safe encryption and Decryption using AES(PyCrypto) and base64.

import base64
from Crypto import Random
from Crypto.Cipher import AES

AKEY = b'mysixteenbytekey' # AES key must be either 16, 24, or 32 bytes long

iv = Random.new().read(AES.block_size)

def encode(message):
    obj = AES.new(AKEY, AES.MODE_CFB, iv)
    return base64.urlsafe_b64encode(obj.encrypt(message))

def decode(cipher):
    obj2 = AES.new(AKEY, AES.MODE_CFB, iv)
    return obj2.decrypt(base64.urlsafe_b64decode(cipher))

If you face some issue like this https://bugs.python.org/issue4329 (TypeError: character mapping must return integer, None or unicode) use str(cipher) while decoding as follows:

return obj2.decrypt(base64.urlsafe_b64decode(str(cipher)))

Test:

In [13]: encode(b"Hello World")
Out[13]: b'67jjg-8_RyaJ-28='

In [14]: %timeit encode("Hello World")
100000 loops, best of 3: 13.9 µs per loop

In [15]: decode(b'67jjg-8_RyaJ-28=')
Out[15]: b'Hello World'

In [16]: %timeit decode(b'67jjg-8_RyaJ-28=')
100000 loops, best of 3: 15.2 µs per loop
Edmundson answered 13/2, 2017 at 19:26 Comment(4)
Bug with Windows x64 + Python 3.6 + PyCryptodome (as pycrypto is deprecated): TypeError: Object type <class 'str'> cannot be passed to C code.Deegan
@Deegan aww sorry.. I don't use windows, so I can't make a fix.Novation
Do not generate the IV and store it at the module level, you need to include the IV in the ciphertext message returned! You now introduced two problems: restarting the Python process gives you a new IV making it impossible to decrypt previously encrypted messages, and in the meantime you are re-using the IV for multiple messages, which effectively reduces the security back to ECB level.Sacci
@AllІѕVаиітy Solved with b'...', I edited the answer for future reference!Deegan
T
13

The library cryptocode provides a simple way to encode and decode strings with a password. Here is how you install:

pip install cryptocode

Encrypting a message (example code):

import cryptocode

encoded = cryptocode.encrypt("mystring","mypassword")
## And then to decode it:
decoded = cryptocode.decrypt(encoded, "mypassword")

Documentation can be found here

Torgerson answered 21/3, 2021 at 3:57 Comment(0)
A
9

Working encode/decode functions in python3 (adapted very little from qneill's answer):

def encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = (ord(clear[i]) + ord(key_c)) % 256
        enc.append(enc_c)
    return base64.urlsafe_b64encode(bytes(enc))

def decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + enc[i] - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)
Aluminium answered 25/2, 2016 at 12:3 Comment(1)
I like how in as series of answers my toy answer has been kept sort of relevant...Embay
O
9

Thanks for some great answers. Nothing original to add, but here are some progressive rewrites of qneill's answer using some useful Python facilities. I hope you agree they simplify and clarify the code.

import base64


def qneill_encode(key, clear):
    enc = []
    for i in range(len(clear)):
        key_c = key[i % len(key)]
        enc_c = chr((ord(clear[i]) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc))


def qneill_decode(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i in range(len(enc)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(enc[i]) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

enumerate()-- pair the items in a list with their index

iterate over the characters in a string

def encode_enumerate(key, clear):
    enc = []
    for i, ch in enumerate(clear):
        key_c = key[i % len(key)]
        enc_c = chr((ord(ch) + ord(key_c)) % 256)
        enc.append(enc_c)
    return base64.urlsafe_b64encode("".join(enc))


def decode_enumerate(key, enc):
    dec = []
    enc = base64.urlsafe_b64decode(enc)
    for i, ch in enumerate(enc):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(ch) - ord(key_c)) % 256)
        dec.append(dec_c)
    return "".join(dec)

build lists using a list comprehension

def encode_comprehension(key, clear):
    enc = [chr((ord(clear_char) + ord(key[i % len(key)])) % 256)
                for i, clear_char in enumerate(clear)]
    return base64.urlsafe_b64encode("".join(enc))


def decode_comprehension(key, enc):
    enc = base64.urlsafe_b64decode(enc)
    dec = [chr((256 + ord(ch) - ord(key[i % len(key)])) % 256)
           for i, ch in enumerate(enc)]
    return "".join(dec)

Often in Python there's no need for list indexes at all. Eliminate loop index variables entirely using zip and cycle:

from itertools import cycle


def encode_zip_cycle(key, clear):
    enc = [chr((ord(clear_char) + ord(key_char)) % 256)
                for clear_char, key_char in zip(clear, cycle(key))]
    return base64.urlsafe_b64encode("".join(enc))


def decode_zip_cycle(key, enc):
    enc = base64.urlsafe_b64decode(enc)
    dec = [chr((256 + ord(enc_char) - ord(key_char)) % 256)
                for enc_char, key_char in zip(enc, cycle(key))]
    return "".join(dec)

and some tests...

msg = 'The quick brown fox jumps over the lazy dog.'
key = 'jMG6JV3QdtRh3EhCHWUi'
print('cleartext: {0}'.format(msg))
print('ciphertext: {0}'.format(encode_zip_cycle(key, msg)))

encoders = [qneill_encode, encode_enumerate, encode_comprehension, encode_zip_cycle]
decoders = [qneill_decode, decode_enumerate, decode_comprehension, decode_zip_cycle]

# round-trip check for each pair of implementations
matched_pairs = zip(encoders, decoders)
assert all([decode(key, encode(key, msg)) == msg for encode, decode in matched_pairs])
print('Round-trips for encoder-decoder pairs: all tests passed')

# round-trip applying each kind of decode to each kind of encode to prove equivalent
from itertools import product
all_combinations = product(encoders, decoders)
assert all(decode(key, encode(key, msg)) == msg for encode, decode in all_combinations)
print('Each encoder and decoder can be swapped with any other: all tests passed')

>>> python crypt.py
cleartext: The quick brown fox jumps over the lazy dog.
ciphertext: vrWsVrvLnLTPlLTaorzWY67GzYnUwrSmvXaix8nmctybqoivqdHOic68rmQ=
Round-trips for encoder-decoder pairs: all tests passed
Each encoder and decoder can be swapped with any other: all tests passed
Overstay answered 20/10, 2017 at 18:10 Comment(2)
Very nice @Nick, good progression of pythonisms, and tests too boot. To give proper credit I was just fixing a bug in Smehmood's original answer https://mcmap.net/q/36312/-simple-way-to-encode-a-string-according-to-a-password.Embay
Any chance for python 3 update? Getting loads of enc_c = chr((ord(clear[i]) + ord(key_c)) % 256) TypeError: ord() expected string of length 1, but int foundStudent
E
7

If you want to be safe, you can use Fernet, which is cryptographically sound. You can use a static "salt" if you don't want to store it separately - you will only lose dictionary and rainbow attack prevention. I chose it because I can pick long or short passwords´, which is not so easy with AES.

from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64

#set password
password = "mysecretpassword"
#set message
message = "secretmessage"

kdf = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt="staticsalt", iterations=100000, backend=default_backend())
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)

#encrypt
encrypted = f.encrypt(message)
print encrypted

#decrypt
decrypted = f.decrypt(encrypted)
print decrypted

If that's too complicated, someone suggested simplecrypt

from simplecrypt import encrypt, decrypt
ciphertext = encrypt('password', plaintext)
plaintext = decrypt('password', ciphertext)
Ethylene answered 10/1, 2017 at 15:51 Comment(1)
Just generate a salt and include it in the encryption result so repeated passwords and messages still result in random output. Include the iterations value too to future-proof the algorithm but still be able to decrypt messages using a different iteration count.Sacci
D
6

I'll give 4 solutions:

1) Using Fernet encryption with cryptography library

Here is a solution using the package cryptography, that you can install as usual with pip install cryptography:

import base64
from cryptography.fernet import Fernet, InvalidToken
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

def cipherFernet(password):
    key = PBKDF2HMAC(algorithm=hashes.SHA256(), length=32, salt=b'abcd', iterations=1000, backend=default_backend()).derive(password)
    return Fernet(base64.urlsafe_b64encode(key))

def encrypt1(plaintext, password):
    return cipherFernet(password).encrypt(plaintext)

def decrypt1(ciphertext, password):
    return cipherFernet(password).decrypt(ciphertext)

# Example:

print(encrypt1(b'John Doe', b'mypass'))  
# b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg=='
print(decrypt1(b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg==', b'mypass')) 
# b'John Doe'
try:  # test with a wrong password
    print(decrypt1(b'gAAAAABd53tHaISVxFO3MyUexUFBmE50DUV5AnIvc3LIgk5Qem1b3g_Y_hlI43DxH6CiK4YjYHCMNZ0V0ExdF10JvoDw8ejGjg==', b'wrongpass')) 
except InvalidToken:
    print('Wrong password')

You can adapt with your own salt, iteration count, etc. This code is not very far from @HCLivess's answer but the goal is here to have ready-to-use encrypt and decrypt functions. Source: https://cryptography.io/en/latest/fernet/#using-passwords-with-fernet.

Note: use .encode() and .decode() everywhere if you want strings 'John Doe' instead of bytes like b'John Doe'.


2) Simple AES encryption with Crypto library

This works with Python 3:

import base64
from Crypto import Random
from Crypto.Hash import SHA256
from Crypto.Cipher import AES

def cipherAES(password, iv):
    key = SHA256.new(password).digest()
    return AES.new(key, AES.MODE_CFB, iv)

def encrypt2(plaintext, password):
    iv = Random.new().read(AES.block_size)
    return base64.b64encode(iv + cipherAES(password, iv).encrypt(plaintext))

def decrypt2(ciphertext, password):
    d = base64.b64decode(ciphertext)
    iv, ciphertext = d[:AES.block_size], d[AES.block_size:]
    return cipherAES(password, iv).decrypt(ciphertext)

# Example:    

print(encrypt2(b'John Doe', b'mypass'))
print(decrypt2(b'B/2dGPZTD8V22cIVKfp2gD2tTJG/UfP/', b'mypass'))
print(decrypt2(b'B/2dGPZTD8V22cIVKfp2gD2tTJG/UfP/', b'wrongpass'))  # wrong password: no error, but garbled output

Note: you can remove base64.b64encode and .b64decode if you don't want text-readable output and/or if you want to save the ciphertext to disk as a binary file anyway.


3) AES using a better password key derivation function and the ability to test if "wrong password entered", with Crypto library

The solution 2) with AES "CFB mode" is ok, but has two drawbacks: the fact that SHA256(password) can be easily bruteforced with a lookup table, and that there is no way to test if a wrong password has been entered. This is solved here by the use of AES in "GCM mode", as discussed in AES: how to detect that a bad password has been entered? and Is this method to say “The password you entered is wrong” secure?:

import Crypto.Random, Crypto.Protocol.KDF, Crypto.Cipher.AES

def cipherAES_GCM(pwd, nonce):
    key = Crypto.Protocol.KDF.PBKDF2(pwd, nonce, count=100000)
    return Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_GCM, nonce=nonce, mac_len=16)

def encrypt3(plaintext, password):
    nonce = Crypto.Random.new().read(16)
    return nonce + b''.join(cipherAES_GCM(password, nonce).encrypt_and_digest(plaintext))  # you case base64.b64encode it if needed

def decrypt3(ciphertext, password):
    nonce, ciphertext, tag = ciphertext[:16], ciphertext[16:len(ciphertext)-16], ciphertext[-16:]
    return cipherAES_GCM(password, nonce).decrypt_and_verify(ciphertext, tag)

# Example:

print(encrypt3(b'John Doe', b'mypass'))
print(decrypt3(b'\xbaN_\x90R\xdf\xa9\xc7\xd6\x16/\xbb!\xf5Q\xa9]\xe5\xa5\xaf\x81\xc3\n2e/("I\xb4\xab5\xa6ezu\x8c%\xa50', b'mypass'))
try:
    print(decrypt3(b'\xbaN_\x90R\xdf\xa9\xc7\xd6\x16/\xbb!\xf5Q\xa9]\xe5\xa5\xaf\x81\xc3\n2e/("I\xb4\xab5\xa6ezu\x8c%\xa50', b'wrongpass'))
except ValueError:
    print("Wrong password")

4) Using RC4 (no library needed)

Adapted from https://github.com/bozhu/RC4-Python/blob/master/rc4.py.

def PRGA(S):
    i = 0
    j = 0
    while True:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        yield S[(S[i] + S[j]) % 256]

def encryptRC4(plaintext, key, hexformat=False):
    key, plaintext = bytearray(key), bytearray(plaintext)  # necessary for py2, not for py3
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]
    keystream = PRGA(S)
    return b''.join(b"%02X" % (c ^ next(keystream)) for c in plaintext) if hexformat else bytearray(c ^ next(keystream) for c in plaintext)

print(encryptRC4(b'John Doe', b'mypass'))                           # b'\x88\xaf\xc1\x04\x8b\x98\x18\x9a'
print(encryptRC4(b'\x88\xaf\xc1\x04\x8b\x98\x18\x9a', b'mypass'))   # b'John Doe'

(Outdated since the latest edits, but kept for future reference): I had problems using Windows + Python 3.6 + all the answers involving pycrypto (not able to pip install pycrypto on Windows) or pycryptodome (the answers here with from Crypto.Cipher import XOR failed because XOR is not supported by this pycrypto fork ; and the solutions using ... AES failed too with TypeError: Object type <class 'str'> cannot be passed to C code). Also, the library simple-crypt has pycrypto as dependency, so it's not an option.

Deegan answered 12/3, 2019 at 22:28 Comment(4)
You do not want to hardcode the salt and iteration counts; generate a random salt and make the iteration count configurable on encryption, include that information in the encryption result, and extract and use the values on decryption. The salt protects you from trivial recognition of reused passwords on a given message, the iteration count future-proofs the algorithm.Sacci
Yes of course @MartijnPieters, but the goal is here to have a simple code for simple purposes, as requested by OP, with two parameters : plain text + password. Of course for more complex scenario (i.e. a database), you will use all these additional parameters.Deegan
There is no need for additional parameters! I include that information encoded in the opaque return value of password_encrypt().Sacci
@MartijnPieters Nice solution indeed.Deegan
J
4

THIS ANSWER IS TERRIBLE FOR SECURITY. DO NOT USE FOR ANYTHING SENSITIVE

Whoever came here (and the bountier) seemed to be looking for one-liners with not much setup, which other answers don't provide. So I'm putting forward base64.

Now, keep in mind that this is basic obfuscation only, and is in NO WAY OK FOR SECURITY, but here are some one-liners:

from base64 import urlsafe_b64encode, urlsafe_b64decode

def encode(data, key): # the key DOES NOT make this safe
    return urlsafe_b64encode(bytes(key+data, 'utf-8'))

def decode(enc, key):
    return urlsafe_b64decode(enc)[len(key):].decode('utf-8')

print(encode('hi', 'there')) # b'dGhlcmVoaQ=='
print(decode(encode('hi', 'there'), 'there')) # 'hi'

A few things to note:

  • you will want to deal with more/less byte-to-string encoding/decoding on your own, depending on your I/O. Look into bytes() and bytes::decode()
  • base64 is easily recognizable by the types of characters used, and often ending with = characters. People like me absolutely go around decoding them in the javascript console when we see them on websites. It's as easy as btoa(string) (js)
  • the order is key+data, as in b64, what characters appear at the end depends on what characters are at the beginning (because of byte offsets. Wikipedia has some nice explanations). In this scenario, the beginning of the encoded string will be the same for everything encoded with that key. The plus is that the data will be more obfuscated. Doing it the other way around will result on the data part being exactly the same for everyone, regardless of key.

Now, if what you wanted didn't even need a key of any kind, but just some obfuscation, you can yet again just use base64, without any kinds of key:

from base64 import urlsafe_b64encode, urlsafe_b64decode

def encode(data):
    return urlsafe_b64encode(bytes(data, 'utf-8'))

def decode(enc):
    return urlsafe_b64decode(enc).decode()

print(encode('hi')) # b'aGk='
print(decode(encode('hi'))) # 'hi'
Juryrigged answered 14/3, 2019 at 6:25 Comment(3)
Yes, if you are not fussed about security, then base64 is way better than to encrypt.Sacci
About the other answers not being one-liners: that’s not the point of the question. They ask for two functions to call. And Fernet(key).encrypt(message) is just one expression just like your base64 call.Sacci
And you should remove the key altogether. Loads of developers are going to copy and paste from Stack Overflow without paying attention and will assume the key to be secret. If you must include it, then at the very least not use it and warn or raise an exception if used anyway. Don’t underestimate the foolishness of the copy-and-paste culture and your responsibilities to deliver sane functions.Sacci
C
3

You can use AES to encrypt your string with a password. Though, you'll want to chose a strong enough password so people can't easily guess what it is (sorry I can't help it. I'm a wannabe security weenie).

AES is strong with a good key size, but it's also easy to use with PyCrypto.

Chaparajos answered 22/3, 2010 at 6:29 Comment(3)
Thanks Alan. But for clarification, I am not encrypting the passwords themselves. In the above example, I am encrypting the string "John Doe" according to the password "mypass", which is a simple password I use in my source code. User passwords are not involved, neither is any other very sensitive information. I edited my question to clarify this.Telegony
AES is great, if used correctly. It is easy to use it incorrectly however; there is at least one answer here that uses an insecure block cipher mode, another two that fumble handling the IV value. Better to use a good library with a well-defined recipe like Fernet!Sacci
Actually, that's a very astute observation. I've fumbled the IV once.Chaparajos
P
3

This works but password length should be exactly 8. This is simple and requires pyDes.

from pyDes import *

def encode(data,password):
    k = des(password, CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
    d = k.encrypt(data)
    return d

def decode(data,password):
    k = des(password, CBC, "\0\0\0\0\0\0\0\0", pad=None, padmode=PAD_PKCS5)
    d = k.decrypt(data)
    return d

x = encode('John Doe', 'mypass12')
y = decode(x,'mypass12')

print x
print y

OUTPUT:

³.\Þ\åS¾+æÅ`;Ê
John Doe
Phlebitis answered 22/3, 2010 at 7:28 Comment(2)
Do not use a fixed IV! Randomise the IV and include it with the ciphertext instead. Otherwise you may as well use ECB mode; repeated plain text messages are otherwise trivial to recognise.Sacci
Also, the pyDes project appears to be dead; the homepage is gone, and the last release on PyPI is now 9 years old.Sacci
C
2

An other implementation of @qneill code which include CRC checksum of the original message, it throw an exception if the check fail:

import struct
import zlib
import base64

def vigenere_encode(text, key):
    text = text.encode() + struct.pack('i', zlib.crc32(text.encode()))
    enc = []
    for i in range(len(text)):
        key_c = key[i % len(key)]
        enc_c = chr((text[i] + ord(key_c)) % 256)
        enc.append(enc_c)

    enc = ''.join(enc).encode()
    enc = base64.urlsafe_b64encode(enc)

    return enc.decode()

def vigenere_decode(encoded_text, key):
    dec = []
    encoded_text = base64.urlsafe_b64decode(encoded_text).decode()
    for i in range(len(encoded_text)):
        key_c = key[i % len(key)]
        dec_c = chr((256 + ord(encoded_text[i]) - ord(key_c)) % 256)
        dec.append(dec_c)

    dec = "".join(dec)
    checksum = dec[-4:]
    dec = dec[:-4]

    crc = struct.pack('i', zlib.crc32(dec.encode()))
    assert [int(i) for i in crc] == [ord(i) for i in checksum], 'Decode Checksum Error'

    return dec
Conveyor answered 28/6, 2016 at 13:51 Comment(1)
any chance for P3 update? erroring in ``` dec_c = chr((256 + ord(encoded_text[i]) - ord(key_c)) % 256) TypeError: ord() expected string of length 1, but int found``` after partial update to p3 and failing further... tia!Student
M
2

Adding one more code with decode and encode for reference

import base64

def encode(key, string):
    encoded_chars = []
    for i in range(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) + ord(key_c) % 128)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    arr2 = bytes(encoded_string, 'utf-8')
    return base64.urlsafe_b64encode(arr2)

def decode(key, string):
    encoded_chars = []
    string = base64.urlsafe_b64decode(string)
    string = string.decode('utf-8')
    for i in range(len(string)):
        key_c = key[i % len(key)]
        encoded_c = chr(ord(string[i]) - ord(key_c) % 128)
        encoded_chars.append(encoded_c)
    encoded_string = "".join(encoded_chars)
    return encoded_string

def main():
    answer = str(input("EorD"))
    if(answer in ['E']):
        #ENCODE
        file = open("D:\enc.txt")
        line = file.read().replace("\n", " NEWLINEHERE ")
        file.close()
        text = encode("4114458",line)
        fnew = open("D:\\new.txt","w+")
        fnew.write(text.decode('utf-8'))
        fnew.close()
    else:
        #DECODE
        file = open("D:\\new.txt",'r+')
        eline = file.read().replace("NEWLINEHERE","\n")
        file.close()
        print(eline)
        eline = eline.encode('utf-8')
        dtext=decode("4114458",eline)
        print(dtext)
        fnew = open("D:\\newde.txt","w+")
        fnew.write(dtext)
        fnew.close

if __name__ == '__main__':
    main()
Montoya answered 22/4, 2020 at 20:19 Comment(0)
I
1

you can use the new PyCryptodomex as PyCrypto is deprecated and has been unmaintained for a while

import base64
from Cryptodome.Cipher import AES
from Cryptodome.Util.Padding import pad, unpad
###################
class Cryptor:
    def __init__(self, key):
      self.SECRET_KEY = str(key).encode("utf-8")
      self.BLOCK_SIZE = 32 # Bytes
      self.CIPHER = AES.new(self.SECRET_KEY, AES.MODE_ECB) # never use ECB in strong systems obviously

    def encrypt(self, text):
       text = str(text).encode("utf-8")
       return base64.b64encode(self.CIPHER.encrypt(pad(text, self.BLOCK_SIZE))).decode("utf-8")
    
    def decrypt(self, encoded_text):
       self.CIPHER = AES.new(self.SECRET_KEY, AES.MODE_ECB)
       return unpad(self.CIPHER.decrypt(base64.b64decode(encoded_text)), self.BLOCK_SIZE).decode("utf-8")
       
cryptor = Cryptor("1234567890123456")
text = "hello world"
text = cryptor.encrypt(text)
print(text)
print(cryptor.decrypt(text))

NOTE: MAKE SURE THAT YOUR KEY IS 16 bytes.

you can install it with pip install pycryptodomex as it is the only version that is now independent from the old PyCrypto library. Note: all modules are installed under the Cryptodome package e.g

from Cryptodome.Cipher import AES

you can read more from official docs here https://www.pycryptodome.org/src/introduction

Inchoative answered 12/3, 2023 at 14:47 Comment(0)
M
0

External libraries provide secret-key encryption algorithms.

For example, the Cypher module in PyCrypto offers a selection of many encryption algorithms:

  • Crypto.Cipher.AES
  • Crypto.Cipher.ARC2
  • Crypto.Cipher.ARC4
  • Crypto.Cipher.Blowfish
  • Crypto.Cipher.CAST
  • Crypto.Cipher.DES
  • Crypto.Cipher.DES3
  • Crypto.Cipher.IDEA
  • Crypto.Cipher.RC5
  • Crypto.Cipher.XOR

MeTooCrypto is a Python wrapper for OpenSSL, and provides (among other functions) a full-strength general purpose cryptography library. Included are symmetric ciphers (like AES).

Monaxial answered 22/3, 2010 at 6:47 Comment(0)
R
0

if you want secure encryption:

for python 2, you should use keyczar http://www.keyczar.org/

for python 3, until keyczar is available, i have written simple-crypt http://pypi.python.org/pypi/simple-crypt

both these will use key strengthening which makes them more secure than most other answers here. and since they're so easy to use you might want to use them even when security is not critical...

Rabbinical answered 3/1, 2013 at 13:30 Comment(1)
From the Keyczar repository: Important note: Keyczar is deprecated. The Keyczar developers recommend Tink, but there is no Python version of Tink.Sacci
O
0

simpciph

Using the so-called Vigenère cipher as a starting point I decided to improve on the idea until I had something which seems properly cryptographic, as the Vigenère cipher is considered relatively easy to crack as n-Caesar ciphers.

This simple cipher has the following characteristics:

  • No 3rd-party libraries; only built-ins
  • Random secret salt (ie. pepper)
  • One-time pad (different each time)
  • Only 32 bytes added to total size, regardless of input size

The following example is designed to be as simple as possible, considering the alternatives, and could be optimized / adapted to various requirements, as needed. In other words, this is primarily intended to illustrate the core functionality of key generation & the cipher...

    import hashlib, secrets
    
    
    def encipher(source_bytes, password):
        source_bytes = bytes(
                secrets.choice(range(256)) for _ in range(32)
                ) + source_bytes
        destination_bytearray = bytearray(len(source_bytes))
        secure_hash = hashlib.sha256(password).digest()
        
        for i, current_byte in enumerate(source_bytes):
            # Rotate hash...
            if i % 32 == 0 and i > 0:
                secure_hash = hashlib.sha256(
                        secure_hash
                        + source_bytes[i-32:i]
                ).digest()
    
            destination_bytearray[i] = (current_byte + secure_hash[i%32]) % 256
    
        return destination_bytearray
    
    
    def decipher(source_bytes, password):
        destination_bytearray = bytearray(len(source_bytes))
        secure_hash = hashlib.sha256(password).digest()
        
        for i, current_byte in enumerate(source_bytes):
            # Rotate hash...
            if i % 32 == 0 and i > 0:
                secure_hash = hashlib.sha256(
                        secure_hash
                        + destination_bytearray[i-32:i]
                ).digest()
    
            destination_bytearray[i] = (current_byte - secure_hash[i%32]) %256
    
        return destination_bytearray[32:]

Usage:

>>> ciphertext = encipher(b'John Doe', b'Passw0rd!')
>>> plaintext = decipher(ciphertext, b'Passw0rd!')
>>> plaintext
bytearray(b'John Doe')

A regular, Unicode text string can be converted to a byte string with:

bytestring = bytes('Some Unicode text string here...', 'utf-8')

And decoded, back into a Unicode text string, with:

unicode = bytestring.decode()
Outman answered 26/11, 2023 at 23:6 Comment(0)
S
-1

So, as nothing mission critical is being encoded, and you just want to encrypt for obsfuscation.

Let me present caeser's cipher

enter image description here

Caesar's cipher or Caesar shift, is one of the simplest and most widely known encryption techniques. It is a type of substitution cipher in which each letter in the plaintext is replaced by a letter some fixed number of positions down the alphabet. For example, with a left shift of 3, D would be replaced by A, E would become B, and so on.

Sample code for your reference :

def encrypt(text,s): 
        result = "" 

        # traverse text 
        for i in range(len(text)): 
            char = text[i] 

            # Encrypt uppercase characters 
            if (char.isupper()): 
                result += chr((ord(char) + s-65) % 26 + 65) 

            # Encrypt lowercase characters 
            else: 
                result += chr((ord(char) + s - 97) % 26 + 97) 

        return result 

    def decrypt(text,s): 
        result = "" 

        # traverse text 
        for i in range(len(text)): 
            char = text[i] 

            # Encrypt uppercase characters 
            if (char.isupper()): 
                result += chr((ord(char) - s-65) % 26 + 65) 

            # Encrypt lowercase characters 
            else: 
                result += chr((ord(char) - s - 97) % 26 + 97) 

        return result 

    #check the above function 
    text = "ATTACKATONCE"
    s = 4
    print("Text  : " + text) 
    print("Shift : " + str(s)) 
    print("Cipher: " + encrypt(text,s))
    print("Original text: " + decrypt(encrypt(text,s),s))

Advantages : it meets your requirements and is simple and does the encoding thing'y'.

Disadvantage : can be cracked by simple brute force algorithms (highly unlikely anyone would attempt to go through all extra results).

Sauers answered 19/3, 2019 at 17:14 Comment(1)
this looks quite nice, but it fails with numbers/- and other symbols in string.Student

© 2022 - 2024 — McMap. All rights reserved.