hmac — Firma y verificación criptográfica de mensajes¶
Propósito: | El módulo hmac implementa el hashing con clave para la autenticación de mensajes, como describe el RFC 2104. |
---|
El algoritmo HMAC se puede utilizar para verificar la integridad de la información transmitida entre aplicaciones o almacenada en un lugar potencialmente vulnerable. La idea básica es generar un hash criptográfico de los datos reales combinados con una clave secreta compartida. El hash resultante puede luego ser utilizado para verificar el mensaje transmitido o almacenado para determinar un nivel de confianza, sin transmitir la clave secreta.
Advertencia
Descargo de responsabilidad: no soy un experto en seguridad. Para los detalles completos soble el HMAC, echa un vistazo a RFC 2104.
Firma de mensajes¶
La función new()
crea un objeto nuevo para calcular un firma de mensaje.
Este ejemplo utiliza el algoritmo hash MD5 predeterminado.
import hmac
digest_maker = hmac.new(b'secret-shared-key-goes-here')
with open('lorem.txt', 'rb') as f:
while True:
block = f.read(1024)
if not block:
break
digest_maker.update(block)
digest = digest_maker.hexdigest()
print(digest)
Cuando el ejecutado, el código lee un archivo de datos y calcula un HMAC firma para él.
$ python3 hmac_simple.py
4bcb287e284f8c21e87e14ba2dc40b16
Tipos de resumen alternativos¶
Aunque el algoritmo criptográfico predeterminado para hmac
es MD5, ese no
es el método más seguro para usar. Los hashes MD5 tienen algunos debilidades,
como las colisiones (donde dos mensajes diferentes producen el mismo hash). El
algoritmo SHA-1 se considera más fuerte, y debe ser utilizado en su lugar.
import hmac
import hashlib
digest_maker = hmac.new(
b'secret-shared-key-goes-here',
b'',
hashlib.sha1,
)
with open('hmac_sha.py', 'rb') as f:
while True:
block = f.read(1024)
if not block:
break
digest_maker.update(block)
digest = digest_maker.hexdigest()
print(digest)
La función new()
toma tres argumentos. El primero es la clave secreta, que
debe compartirse entre los dos puntos finales que están comunicando para que
ambos extremos puedan usar el mismo valor. El segundo valor es un mensaje
inicial. Si el contenido del mensaje que necesita ser autenticado es pequeño,
como una marca de tiempo o HTTP POST, todo el cuerpo del mensaje se puede pasar
a new()
en lugar de usar el método update()
. El último argumento es el
módulo de resumen a ser usado. El valor predeterminado es hashlib.md5
.
Este ejemplo pasa 'sha1'
, causando que hmac
use hashlib.sha1
$ python3 hmac_sha.py
dcee20eeee9ef8a453453f510d9b6765921cf099
Resúmenes binarios¶
Los ejemplos anteriores usaron el método hexdigest()
para producir
resúmenes imprimibles. El resumen hexadecimal es una representación diferente
de la valor calculado por el método digest()
, que es un valor binario que
puede incluir caracteres no imprimibles, incluyendo NUL
. Algunos servicios
Web (Google Checkout, Amazon S3) utilizan la versión codificada en base64 del
resumen binario en lugar del resumen hexadecimal.
import base64
import hmac
import hashlib
with open('lorem.txt', 'rb') as f:
body = f.read()
hash = hmac.new(
b'secret-shared-key-goes-here',
body,
hashlib.sha1,
)
digest = hash.digest()
print(base64.encodestring(digest))
La cadena codificada en base64 termina en una nueva línea, que con frecuencia necesita se eliminada al insertar la cadena en los encabezados http u otros contextos sensibles al formateo.
$ python3 hmac_base64.py
b'olW2DoXHGJEKGU0aE9fOwSVE/o4=\n'
Aplicaciones de firmas de mensajes¶
La autenticación HMAC debe utilizarse para cualquier servicio de red pública, y
en cualquier momento que los datos se almacenan donde la seguridad es
importante. Por ejemplo, cuando el envío de datos a través de una red o un
conector, dichos datos deben estar firmados y luego se debe probar la firma antes
de utilizar los datos. El ejemplo extendido dado aquí está disponible en el
archivo hmac_pickle.py
.
El primer paso es establecer una función para calcular un resumen para una cadena, y una clase simple para ser instanciada y pasada a través de un canal de comunicación.
import hashlib
import hmac
import io
import pickle
import pprint
def make_digest(message):
"Return a digest for the message."
hash = hmac.new(
b'secret-shared-key-goes-here',
message,
hashlib.sha1,
)
return hash.hexdigest().encode('utf-8')
class SimpleObject:
"""Demonstrate checking digests before unpickling.
"""
def __init__(self, name):
self.name = name
def __str__(self):
return self.name
A continuación, crea un búfer BytesIO
para representar el conector o
pipe. El ejemplo utiliza un formato ingenuo, pero fácil de analizar, para el
flujo de datos. El resumen y la longitud de los datos se escriben, seguidos de
un línea nueva. La representación serializada del objeto, generada por
pickle
, sigue. Un sistema real no querría depender de un valor de
longitud, ya que si el resumen es incorrecto, la longitud está también
probablemente mal. Algún tipo de secuencia de terminador que no es probable
que aparezca en los datos reales sería más apropiada.
El programa de ejemplo luego escribe dos objetos en el flujo. El primero se escribe utilizando el valor de resumen correcto.
# Simulate a writable socket or pipe with a buffer
out_s = io.BytesIO()
# Write a valid object to the stream:
# digest\nlength\npickle
o = SimpleObject('digest matches')
pickled_data = pickle.dumps(o)
digest = make_digest(pickled_data)
header = b'%s %d\n' % (digest, len(pickled_data))
print('WRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)
El segundo objeto se escribe en el flujo con un resumen no válido, producido mediante el cálculo del resumen de algunos otros datos en lugar de la serialización.
# Write an invalid object to the stream
o = SimpleObject('digest does not match')
pickled_data = pickle.dumps(o)
digest = make_digest(b'not the pickled data at all')
header = b'%s %d\n' % (digest, len(pickled_data))
print('\nWRITING: {}'.format(header))
out_s.write(header)
out_s.write(pickled_data)
out_s.flush()
Ahora que los datos están en el búfer BytesIO
, se pueden leer de nuevo.
Comienza leyendo la línea de datos con el resumen y la longitud de los datos.
Luego lee los datos restantes, usando la longitud valor. pickle.load()
podría leer directamente del flujo, pero que asume un flujo de datos de
confianza y estos datos aún no son de confianza suficiente para deshacerlos.
Leer la serialización como una cadena del flujo, sin desentrañar realmente el
objeto, es más seguro.
# Simulate a readable socket or pipe with a buffer
in_s = io.BytesIO(out_s.getvalue())
# Read the data
while True:
first_line = in_s.readline()
if not first_line:
break
incoming_digest, incoming_length = first_line.split(b' ')
incoming_length = int(incoming_length.decode('utf-8'))
print('\nREAD:', incoming_digest, incoming_length)
Una vez que los datos están de serializados la memoria, el valor de resumen
puede ser recalculado y comparado con los datos leídos usando
compare_digest()
. Si los resúmenes coinciden, es seguro confiar en el
datos y descomprimirlos.
incoming_pickled_data = in_s.read(incoming_length)
actual_digest = make_digest(incoming_pickled_data)
print('ACTUAL:', actual_digest)
if hmac.compare_digest(actual_digest, incoming_digest):
obj = pickle.loads(incoming_pickled_data)
print('OK:', obj)
else:
print('WARNING: Data corruption')
La salida muestra que el primer objeto se verifica y el segundo se considera «corrupto», como se esperaba.
$ python3 hmac_pickle.py
WRITING: b'f49cd2bf7922911129e8df37f76f95485a0b52ca 69\n'
WRITING: b'b01b209e28d7e053408ebe23b90fe5c33bc6a0ec 76\n'
READ: b'f49cd2bf7922911129e8df37f76f95485a0b52ca' 69
ACTUAL: b'f49cd2bf7922911129e8df37f76f95485a0b52ca'
OK: digest matches
READ: b'b01b209e28d7e053408ebe23b90fe5c33bc6a0ec' 76
ACTUAL: b'2ab061f9a9f749b8dd6f175bf57292e02e95c119'
WARNING: Data corruption
La comparación de dos resúmenes con una cadena simple o una comparación de
bytes puede ser utilizada en un ataque de tiempo para exponer parte o la
totalidad de la clave secreta pasando resúmenes de diferentes longitudes.
compare_digest()
implementa una función de comparación rápida pero de
tiempo constante para proteger contra los ataques de tiempo.
Ver también
- Documentación de la biblioteca estándar para hmac
- RFC 2104 – HMAC: Key-Hashing para la autenticación de mensajes
hashlib
– El módulohashlib
proporciona generadores de hash MD5 y SHA1.pickle
– Biblioteca de serialización.- WikiPedia: MD5 – Descripción del algoritmo MD5 de hashing.
- Firma y autenticación de solicitudes REST (Amazon AWS) – Instrucciones para la autenticación en S3 utilizando credenciales firmadas con HMAC-SHA1.