Twofish шифрование в Python

У меня есть инструмент тестирования, который использует Twofish в качестве алгоритма шифрования для шифрования данных перед их отправкой на сервер. Код написан на C++ и использует оптимизированную реализацию Брюса Шнайера на C ( https://www.schneier.com/code/twofish-optimized-c.zip). Мне нужно перенести этот инструмент на Python, и я использую модуль twofish ( https://pypi.python.org/pypi/twofish/0.3.0). Я могу зашифровать и дешифровать строки длиной 16 символов, но для других длин строк это выдает ошибку "ValueError: недопустимая длина блока".

Как я могу зашифровать и расшифровать большие данные с помощью модуля Python Twofish?

>>> from twofish import Twofish
>>> key = binascii.unhexlify('8CACBE276491F6FF4B1EC0E9CFD52E76')
>>> t = Twofish(key)
>>> cipher_text = T.encrypt('deadbeaf12345678')
>>> plain_text = t.decrypt(cipher_text)
>>> plain_text
'deadbeaf12345678'
>>> cipher_text = t.encrypt('deadbeaf12345678hello world 1234')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python2.7/site-packages/twofish.py", line 69, in encrypt
    raise ValueError('invalid block length')
ValueError: invalid block length

Обновление: я пробую другое решение этой проблемы. Я создал Windows DLL, twofish.dll из оптимизированной реализации Си Брюса Шнайера ( https://www.schneier.com/code/twofish-optimized-c.zip). Кроме того, я экспортировал функции-оболочки для функций-членов кодирования и декодирования, используя __declspec(dllexport).

Я загружаю эту DLL в Python, используя функцию ctype.CDLL. Прототип для функции кодирования:

__declspec(dllexport) int encode(unsigned char *key, unsigned char *in, unsigned int inlen, unsigned char *out, unsigned int outbuflen, unsigned int& outlen)

Как определить типы аргументов в скрипте Python?

import ctypes
import binascii
import requests

twofish_dll = ctypes.CDLL("twofish.dll")

encode = twofish_dll.encode

f = open('test.xml', 'r')
plain_text = f.read()
f.close()

cipher_text = ctypes.create_string_buffer(8192)
cipher_text_lenght = (ctypes.c_uint)()

KCS = '8CACBE276491F6FF4B1EC0E9CFD52E76'
key = binascii.unhexlify(KCS)

encode.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint)]
encode(ctypes.c_char_p(key), ctypes.c_char_p(plain_text), len(plain_text), cipher_text, 8192, ctypes.byref(cipher_text_lenght))

При выполнении вышеприведенный код выдает ошибку ниже:

Traceback (most recent call last):
  File "C:\Data\sepm_test.py", line 21, in <module>
    encode(ctypes.c_char_p(key), ctypes.c_char_p(plain_text), len(plain_text), cipher_text, 8192, ctypes.byref(cipher_text_lenght))
TypeError: bytes or integer address expected instead of str instance

2 ответа

Решение

Я наконец решил эту проблему, скомпилировав оптимизированную реализацию Брюса Шнайера для C для Twofish ( https://www.schneier.com/code/twofish-optimized-c.zip) в DLL и загрузив эту DLL с помощью модуля ctypes.

import ctypes
import binascii
import requests

twofish_dll = ctypes.CDLL("twofish.dll")

encode = twofish_dll.encode

f = open('test.xml', 'r')
plain_text = f.read()
f.close()
plain_text_buffer = ctypes.create_string_buffer(str.encode(plain_text))
plain_text_buffer_length = (ctypes.c_uint)(len(plain_text_buffer))

cipher_text_buffer = ctypes.create_string_buffer(8192)
cipher_text_buffer_length = (ctypes.c_uint)(len(cipher_text_buffer))
cipher_text_length = ctypes.c_uint(0)

KCS = '8CACBE276491F6FF4B1EC0E9CFD52E76'
key = binascii.unhexlify(KCS)

encode.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.c_uint, ctypes.POINTER(ctypes.c_uint)]
encode.restype = ctypes.c_int

encode(ctypes.c_char_p(key), plain_text_buffer, plain_text_buffer_length, cipher_text_buffer, cipher_text_buffer_length, ctypes.pointer(cipher_text_length))

Twofish - это блочный шифр, который шифрует только 16 октетов за раз. Quoth документация:

Создайте экземпляр twofish.Twofish с ключом длины]0, 32], а затем используйте методы шифрования и дешифрования для 16-байтовых блоков.

Все значения должны быть двоичными строками (str на Python 2, байты на Python 3)

[ПРЕДУПРЕЖДЕНИЕ] это следует использовать в разумном режиме шифрования, таком как CTR или CBC. Если вы не знаете, что это значит, вам следует использовать библиотеку более высокого уровня.

Обратите внимание на предупреждение: CBC или CTR - это не ракетостроение, но если вы используете Twofish наивно, его безопасность подвергается ужасной угрозе.

'''
The encryption algo is "Twofish Encryption"
'''

import sys
from twofish import Twofish
import os

def usages():
    print('-------Usgaes of TwoFish encrypt/decrypt script------------')
    print('')
    print('    --help or -h to get help of the script')
    print('    --encrypt or -e to encrypt a file')
    print('    --decrypt or -d to decrypt a file')
    print('    --filename or -f filename to be encrypted or decrypted')
    print('    --keypath or -k filename to pass keyfile name')
    print('')
    print('python TwofishEncryptDecrypt.py --encrypt --filename <FileName> --keypath <KeyFilename>')
    print('python TwofishEncryptDecrypt.py --decrypt --filename <FileName> --keypath <KeyFilename>')
    print('')
    print('-----------------------------End---------------------------')

def argumentParse(argsList):
    if '--help' in argsList or '-h' in argsList:
        usages()
        sys.exit(0)
    elif ('-e' in argsList and '-d' in argsList) or ('--encrypt' in argsList and '--decrypt' in argsList):
        print('Both action not allowed together')
        usages()
        sys.exit(0)

    i = 0
    for item in argsList:
        if item == '-e' or item == '--encrypt':
            arguments.update({'action': 'encryption'})
        elif item == '-d' or item == '--decrypt':
            arguments.update({'action': 'decryption'})
        elif item == '-f' or item == '--filename':
            if os.path.exists(argsList[i+1]) and os.path.getsize(argsList[i+1])>0:
                arguments.update({'filename': argsList[i+1]})
            else:
                print("[No such file or Directory] or [File Might Be empty] :",argsList[i+1])
                pass
        elif item == '-k' or item == '--keypath':
            arguments.update({'keypath': argsList[i+1]})
        else:
            pass
        i+=1

    return arguments

class encryptDecrypt():

    BLOCK_SIZE = 16
    twofish_passphrase = ''

    def encryptTwofish(self, filename):
        fileContent = open(filename,'r').read()

        paddingBytesLength = self.BLOCK_SIZE - (len(fileContent) % self.BLOCK_SIZE)

        paddingBytes = ''
        for i in range(paddingBytesLength):
            #paddingBytes += '00'
            paddingBytes += ' '

        #fileContent = fileContent + bytearray.fromhex(paddingBytes).decode("utf-8")
        fileContent = fileContent.decode('utf-8') + paddingBytes

        iteration_count = int(len(fileContent) / self.BLOCK_SIZE)
        encryptedFileContent = ''.encode()
        for i in range(iteration_count):
            encryptedFileContent += self.T.encrypt(fileContent[self.BLOCK_SIZE * i : (i+1) * self.BLOCK_SIZE].encode())

        return encryptedFileContent

    def decryptTwofish(self, filename):
        decryptedFileContent = ''
        encryptedFileContent = open(filename,'rb').read()

        iteration_count = int(len(encryptedFileContent) / self.BLOCK_SIZE)

        for i in range(iteration_count):
            decryptedFileContent += self.T.decrypt(encryptedFileContent[self.BLOCK_SIZE * i : (i+1) * self.BLOCK_SIZE]).decode()

        return decryptedFileContent.strip()

    def readKey(self, path):
        keyValue = open(path,'r')
        for line in keyValue:
            self.twofish_passphrase=line.rstrip('\n')
            self.T = Twofish(self.twofish_passphrase)

# entry point
    def writeFile(self, filename, fileContent):
        resultFile = open(filename,'wb')
        resultFile.write(fileContent)
        resultFile.close()

if __name__ == "__main__":
    arguments = {}
    argsList = sys.argv
    arguments = argumentParse(argsList)

    if not 'keypath' in arguments or not 'action' in arguments or not 'filename' in arguments:
        usages()
        sys.exit()

    twofish_encrypt = encryptDecrypt()
    twofish_encrypt.readKey(arguments['keypath'])
    if (arguments['action'] == 'encryption'):
        twofish_encrypt.writeFile(arguments['filename'], twofish_encrypt.encryptTwofish(arguments['filename']))
    elif (arguments['action'] == 'decryption'):
        twofish_encrypt.writeFile(arguments['filename'], twofish_encrypt.decryptTwofish(arguments['filename']))

# Remove ending NULL character from decrypted string
Другие вопросы по тегам