Source code for railgun.common.crypto

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# @file: railgun/common/crypto.py
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# This file is released under BSD 2-clause license.

from Crypto import Random
from Crypto.Cipher import AES


[docs]class AESCipher(object): """Wrap the AES encryption algorithm so that you can use secret keys in any size to encrypt a plain text in any length, and decrypt it backwards. The technical parameters of this AES cipher is: .. tabularcolumns:: |p{5cm}|p{10cm}| =============== ======================================== Parameter Description =============== ======================================== BlockSize 16 IVSize Equal to `BlockSize`. KeySize 32 =============== ======================================== Since AES cipher is a block cipher, the size of plain text must be a multiple of `BlockSize` bytes. :class:`AESCipher` would pad the input text to a multiple of `KeySize` bytes by following method: * Calculate the count of padded bytes: ``KeySize - len(data) % KeySize``. We mark this count as `padsize`. * Append ``chr(padsize) * padsize`` (`padsize` of `chr(padsize)` characters) to the tail of input text. * Encrypt on such input text. We mark this `ciphertext`. AES cipher relies on not only the `key` to decrypt a cipher text, but also the `initial vector` to do the decryption. So we must prepend `IV` to the front of cipher text. Thus the final encrypted text should be:: `initial vector` (`IVSize` bytes) + `ciphertext` (padded) Any implementation of AES cipher in the runner hosts must be compatible with this method. :param key: Encryption and decryption key for AES algorithm. If `len(key)` != 32, it will be padded or truncated. :type key: :class:`str` """ def __init__(self, key): """Construct a new :class:`AESCipher` with given `key`.""" # AES encryption parameters self.BlockSize = AES.block_size self.IVSize = self.BlockSize self.KeySize = 32 # pad the key if len(key) >= self.KeySize: self.key = key[:self.KeySize] else: self.key = self._padkey(key)
[docs] def encrypt(self, raw): """Encrypt the `raw` text. :param raw: Plain text to be encrypted. :type raw: :class:`str` """ raw = self._pad(raw) iv = Random.new().read(self.IVSize) cipher = AES.new(self.key, AES.MODE_CBC, iv) return iv + cipher.encrypt(raw)
[docs] def decrypt(self, enc): """Decrypt the `enc` text. :param enc: Cipher text to be decrypted. :type enc: :class:`str` """ iv = enc[:self.IVSize] cipher = AES.new(self.key, AES.MODE_CBC, iv) return self._unpad(cipher.decrypt(enc[self.IVSize:]))
def _padkey(self, s): padsize = self.KeySize - len(s) % self.KeySize return s + (chr(0) * padsize) def _pad(self, s): padsize = self.BlockSize - len(s) % self.BlockSize return s + (chr(padsize) * padsize) def _unpad(self, s): if not s or len(s) % self.BlockSize != 0: raise ValueError("`s` is not a padded string.") return s[:-ord(s[-1])]
[docs]def DecryptMessage(s, key): """Convenient method to encrypt `s` with `key`. :param s: Cipher text to be decrypted. :type s: :class:`str` :param key: Key to be used in AES algorithm. :type key: :class:`str` """ return AESCipher(key).decrypt(s)
[docs]def EncryptMessage(s, key): """Convenient method to decrypt `s` with `key`. :param s: Plain text to be encrypted. :type s: :class:`str` :param key: Key to be used in AES algorithm. :type key: :class:`str` """ return AESCipher(key).encrypt(s)