[PYTHON] Raisons pour lesquelles le cryptage hybride est utilisé (comparaison des vitesses de cryptage / décryptage)

Il est connu que le chiffrement / déchiffrement par la méthode de clé publique (RSA) prend du temps et un chiffrement hybride est utilisé. J'ai en fait mesuré la vitesse de cryptage et de décryptage, également comme mémo sur la façon de crypter et de décrypter avec un programme (Python).

1. Types de cryptage

1.1. Méthode de chiffrement symétrique (clé commune)

Une méthode qui utilise la même clé pour le chiffrement et le déchiffrement.

AES (chiffrement en bloc)

La longueur du bloc est fixée à 128 bits et la longueur de la clé peut être de 128 bits, 192 bits ou 256 bits.

Mode cryptographique

Une méthode pour crypter des informations plus longues que la longueur de bloc avec un cryptage par bloc.

1.2. Méthode de chiffrement asymétrique (clé publique)

Méthode de chiffrement avec une clé publique et de déchiffrement avec une clé privée.

Cryptage RSA

Cryptographie qui utilise la difficulté des nombres logarithmiques discrets et la factorisation de grands nombres premiers. Cryptographique = (plain ** E)% N La paire {E, N} de est la clé publique au moment du chiffrement. Simple = (crypté ** D)% N La paire {D, N} de est la clé privée au moment du déchiffrement. Utilisez un nombre de 2048 bits ou plus comme N. 4096 bits ou plus (réf. NIST SP800-57) pour une nouvelle utilisation après 2031. Même avec la méthode de chiffrement à clé publique, si une personne malveillante entre entre l'expéditeur et le destinataire de la communication, la communication peut être interrompue (attaque MITM (man-in-the-middle)). Un certificat de clé publique est utilisé pour éviter cela. RSA-OAEP utilise des nombres aléatoires pour générer à chaque fois différents chiffrements pour le même texte brut.

Code de courbe elliptique

Des clés encore plus courtes sont plus puissantes que RSA (le code de courbe elliptique de clés de 224 à 225 bits équivaut à la même force que RSA 2048 bits).

2. Chiffrement hybride (combinaison de la méthode de la clé publique et de la méthode de la clé commune)

Lors du chiffrement d'un fichier volumineux de plusieurs Mo ou plus, la méthode de la clé commune est utilisée car le chiffrement et le déchiffrement de la méthode de la clé publique prennent beaucoup de temps. La méthode de la clé publique est utilisée pour communiquer la clé commune utilisée à ce moment. On dit que le même niveau de force de cryptage est préférable pour les deux, Publication spéciale NIST 800-57 Partie 1 Le tableau 2 de la révision 4, «Recommandations pour la gestion des clés» répertorie les points forts de sécurité pouvant être utilisés comme référence.

Force de sécurité Algorithme de clé privée (clé commune) IFC (par exemple RSA)
128 AES-128 k = 3072
192 AES-192 k = 7680
256 AES-256 k = 15360

3. Comparaison du temps de traitement réel

Les temps de traitement de l'AES-256 et du RSA-2048 ont été comparés. (Bien que la force soit différente ...)

Environnement d'exploitation

3.1 Chiffrement et déchiffrement avec la méthode de chiffrement symétrique (AES-256)

Reportez-vous à ce site et ressemblez à l'AES256.py suivant (Il est nécessaire de supprimer le remplissage lors de son utilisation réelle pour le cryptage / décryptage de fichiers).

Lorsque vous exécutez AES256.py illustré ci-dessous,


$ python3 AES256.py 
File size =  10.0 [MB]
Encode:
AES_encrypt_time:0.12138915061950684[sec]
Decode:
AES_decrypt_time:0.12209415435791016[sec]

Le résultat est le suivant. On peut voir qu'il faut environ 0,12 seconde pour crypter et décrypter un fichier de 10 Mo.

AES256.py


import sys,struct,random
from Crypto.Cipher import AES
from hashlib import sha256
import time
import os

def generate_salt(digit_num):
    DIGITS_AND_ALPHABETS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
    return "".join(random.sample(DIGITS_AND_ALPHABETS, digit_num))

# AES-256
def derive_key_and_iv(password, salt, bs):
    salted = ''.encode()
    dx = ''.encode()
    #AES à partir du mot de passe-Clé pour 256 et vecteur initial pour CBC(iv)Générer un
    while len(salted) < 48: # 48 =Longueur de clé AES256(32byte)+Longueur IV(16byte)
        hash = dx + password.encode() + salt.encode()
        dx = sha256(hash).digest()
        salted = salted + dx
    key = salted[0:32] # 32byte -> AES-256 longueurs de clé
    iv = salted[32:48] # 16byte (AES.block_Même taille que la taille,128 bits en AES(=16byte)Fixé)
    return key, iv

#chiffrement
def encrypt(in_file, out_file, password):
    bs = AES.block_size
    #salt = generate_salt() #Random.new().read(bs - len('Salted__'))
    salt = generate_salt(AES.block_size)
    key, iv = derive_key_and_iv(password, salt, bs)

    cipher = AES.new(key, AES.MODE_CBC, iv) #Réglez le mode CBC. Obtenez la classe AESCipher.
    out_file.write(('Salted__' + salt).encode()) #salt écrit dans un fichier chiffré
    finished = False
    while not finished:
        chunk = in_file.read(1024 * bs)
        orgChunkLen = len(chunk)
        if len(chunk) == 0 or len(chunk) % bs != 0:
            padding_length = (bs - len(chunk) % bs) or bs
            padding = padding_length * chr(padding_length)
            chunk += padding.encode()
            finished = True
        if len(chunk) > 0:
            out_file.write(cipher.encrypt(chunk))

#Décryptage
def decrypt(in_file, out_file, password):
    bs = AES.block_size
    in_file.seek(len('Salted__'))
    salt = in_file.read(16).decode()
    #clé de sel et mot de passe,Obtenez iv.
    key, iv = derive_key_and_iv(password, salt, bs)

    cipher = AES.new(key, AES.MODE_CBC, iv) #Réglez le mode CBC. Obtenez la classe AESCipher.
    finished = False

    while not finished:
        chunk = in_file.read(1024 * bs)
        orgChunkLen = len(chunk)
        if orgChunkLen == 0 or orgChunkLen % bs != 0:
            padding_length = (bs - orgChunkLen % bs) or bs
            padding = padding_length * chr(padding_length)
            chunk += padding.encode()
            finished = True
        if orgChunkLen > 0:
            out_file.write(cipher.decrypt(chunk)[0:orgChunkLen])

def main(filename):
    infile = open(filename, "rb")
    outfile = open(filename+"_AES.bin", "wb")

    print("File size = ", os.path.getsize(filename) /1024/1024, "[MB]")
    print("Encode:")
    start = time.time()
    encrypt(infile,outfile,"password")
    # openssl enc -e -aes-256-cbc -salt -k "password" -in practice.bin -out practice_aes.bin
    elapsed_time = time.time() - start
    print ("AES_encrypt_time:{0}".format(elapsed_time) + "[sec]")

    infile.close()
    outfile.close()

    print("Decode:")

    outinfile = open(filename+"_AES.bin", "rb")
    outfile2 = open(filename+"_dec_AES.bin", "wb")

    start = time.time()
    decrypt(outinfile,outfile2,"password")
    elapsed_time = time.time() - start
    print ("AES_decrypt_time:{0}".format(elapsed_time) + "[sec]")

    outinfile.close()
    outfile2.close()

if __name__== "__main__":
  filename = "practice.bin"
  main(filename)

3.2. Chiffrement et déchiffrement par méthode de chiffrement asymétrique (clé publique) (RSA)

Fondamentalement, RSA n'est pas censé être utilisé pour crypter des données de grande taille. Il a été implémenté en morceaux pour la mesure de la vitesse, mais il n'est généralement pas recommandé. Les données de grande taille sont cryptées à l'aide d'un cryptage symétrique tel qu'AES.

Code modifié en référence à ce site et fragmenté 196 octets. Changé pour chiffrer en unités.

Lorsque vous exécutez RSA2048.py illustré ci-dessous,


$ python3 RSA2048.py 
max_data_len= 196
Generate Key:
privatePem len =  1674
publicPem len =  450
Load Key:
File size =  10.0 [MB]
Encode:
RSA_encrypt_time:46.78378772735596[sec]
Decode:
RSA_decrypt_time:123.22586274147034[sec]

Le résultat est le suivant. Vous pouvez voir qu'il faut environ 47 secondes pour crypter un fichier de 10 Mo et environ 123 secondes pour le décrypter. Étant donné que chaque AES a pris environ 0,12 seconde, on peut voir que RSA est environ 400 fois plus lent pour le cryptage et environ 1000 fois plus lent pour le décryptage.

RSA2048.py



from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
import base64
import time
import os

modulus_length = 2048 # bit
max_data_len = int((int(modulus_length/8.0) - 11 )*0.8)  #La taille maximale pouvant être chiffrée avec RSA est inférieure à la taille de la clé. Dépend de ce que vous utilisez pour le rembourrage.
print("max_data_len=",max_data_len)

def generate_keys():
    key = RSA.generate(modulus_length)
    pub_key = key.publickey()
    return key, pub_key

def encrypt_private_key(a_message, private_key):
    encryptor = PKCS1_OAEP.new(private_key)
    encrypted_msg = encryptor.encrypt(a_message)
    encoded_encrypted_msg = base64.b64encode(encrypted_msg)
    return encoded_encrypted_msg

def decrypt_public_key(encoded_encrypted_msg, public_key):
    encryptor = PKCS1_OAEP.new(public_key)
    decoded_encrypted_msg = base64.b64decode(encoded_encrypted_msg)
    decoded_decrypted_msg = encryptor.decrypt(decoded_encrypted_msg)
    return decoded_decrypted_msg

def encrypt_file(in_file, out_file, key):
    finished =False
    while not finished:
        chunk = in_file.read(max_data_len)
        if len(chunk) == 0 or len(chunk)%max_data_len:
            finished = True
        encdata = encrypt_private_key(chunk, key)
        a_number = len(encdata)
        out_file.write(a_number.to_bytes(4, byteorder='little'))
        out_file.write(encdata)
    out_file.close()

def decrypt_file(in_file, out_file, key):
    finished =False
    while not finished:
        bnum = in_file.read(4)
        inum = int.from_bytes(bnum, byteorder='little')
        chunk = in_file.read(inum)
        if len(chunk) == 0 or len(chunk)%inum:
            finished = True
        if len(chunk) != 0:
          decdata = decrypt_public_key(chunk, key)
          out_file.write(decdata[0:len(chunk)])
    out_file.close()

def main(filename):
    print("Generate Key:")
    private, public = generate_keys()
    privateFile = open("private.pem","wb")
    privatePem = private.exportKey(format='PEM')
    print("privatePem len = ", len(privatePem))
    privateFile.write(privatePem)
    privateFile.close()
    publicFile = open("public.pem","wb")
    publicPem = public.exportKey(format='PEM')
    print("publicPem len = ", len(publicPem))
    publicFile.write(publicPem)
    publicFile.close()
    #print (private)
    #message = b'AES password or key'
    #print(message)
    #encoded = encrypt_private_key(message, public)
    #decrypt_public_key(encoded, private)

    print("Load Key:")
    privateFile = open("private.pem","rb")
    private_pem = privateFile.read()
    privateFile.close()
    private_key = RSA.importKey(private_pem)
    publicFile = open("public.pem","rb")
    public_pem = publicFile.read()
    publicFile.close()
    public_key = RSA.importKey(public_pem)

    print("File size = ", os.path.getsize(filename) /1024/1024, "[MB]")

    print("Encode:")
    infile = open(filename, "rb")
    outfile = open(filename+"_RSA.bin", "wb")
    start = time.time()
    encrypt_file(infile,outfile,public_key)
    elapsed_time = time.time() - start
    print ("RSA_encrypt_time:{0}".format(elapsed_time) + "[sec]")
    infile.close()
    outfile.close()

    print("Decode:")
    infile = open(filename+"_RSA.bin", "rb")
    outfile = open(filename+"_dec_RSA.bin", "wb")
    start = time.time()
    decrypt_file(infile,outfile,private_key)
    elapsed_time = time.time() - start
    print ("RSA_decrypt_time:{0}".format(elapsed_time) + "[sec]")
    infile.close()
    outfile.close()


if __name__== "__main__":
    filename = "practice.bin"
    main(filename)

4. Résumé

Dans la TSL et PGP (GnuPG), la clé de la méthode de cryptage symétrique (données équivalentes) est échangée par la méthode de clé publique, et le cryptage symétrique est utilisé pour les données réelles, mais cela a une grande différence de vitesse de traitement et est ouvert au public On dit que le cryptage / décryptage de la méthode de clé prend du temps. Ici, afin de confirmer à quel point la vitesse de traitement diffère réellement au niveau de la commande, nous avons créé et comparé le code de test Python pour la méthode de cryptage symétrique (AES) et la méthode de clé publique (RSA). En raison de la mesure réelle, il a fallu environ 0,12 seconde à AES pour crypter / décrypter un fichier de 10 Mo, tandis que RSA prend environ 47 secondes pour crypter, ce qui est environ 400 fois plus lent, et le décryptage est d'environ 123. Il s'est avéré être environ 1000 fois plus lent en quelques secondes.

Livres de référence

Recommended Posts

Raisons pour lesquelles le cryptage hybride est utilisé (comparaison des vitesses de cryptage / décryptage)
FAQ: Pourquoi la comparaison des nombres est-elle incohérente?
Pourquoi l'entropie croisée est-elle utilisée pour la fonction objective du problème de classification?
Comparaison de la vitesse de transposition de la matrice par Python