Protocolo de contraseña remota segura
El protocolo de contraseña remota segura (SRP) es un protocolo de intercambio de claves autenticado por contraseña (PAKE) aumentado, diseñado específicamente para evitar patentes existentes.
Como todos los protocolos PAKE, un espía o un intermediario no puede obtener suficiente información para poder adivinar una contraseña por fuerza bruta o aplicar un ataque de diccionario sin más interacciones con las partes para cada suposición. Además, al ser un protocolo PAKE aumentado, el servidor no almacena datos equivalentes a contraseñas. Esto significa que un atacante que roba los datos del servidor no puede hacerse pasar por el cliente a menos que primero realice una búsqueda de fuerza bruta de la contraseña.
En términos sencillos, durante la autenticación SRP (o cualquier otro protocolo PAKE), una parte (el "cliente" o "usuario") demuestra a otra parte (el "servidor") que conocen la contraseña, sin enviar la contraseña en sí ni ninguna otra información de la que se pueda derivar la contraseña. La contraseña nunca sale del cliente y el servidor la desconoce.
Además, el servidor también necesita conocer la contraseña (pero no la contraseña en sí) para iniciar la conexión segura. Esto significa que el servidor también se autentica ante el cliente, lo que evita el phishing sin depender de que el usuario analice URL complejas.
La única propiedad de seguridad matemáticamente probada de SRP es que es equivalente a Diffie-Hellman contra un atacante pasivo. Los PAKE más nuevos, como AuCPace y OPAQUE, ofrecen garantías más sólidas.
Descripción general
El protocolo SRP tiene una serie de propiedades deseables: permite a un usuario autenticarse en un servidor, es resistente a ataques de diccionario montados por un espía y no requiere un tercero de confianza. Transmite eficazmente una prueba de contraseña sin conocimiento del usuario al servidor. En la revisión 6 del protocolo sólo se puede adivinar una contraseña por intento de conexión. Una de las propiedades interesantes del protocolo es que incluso si una o dos de las primitivas criptográficas que utiliza son atacadas, sigue siendo seguro. El protocolo SRP ha sido revisado varias veces y actualmente se encuentra en la revisión 6a.
El protocolo SRP crea una gran clave privada compartida entre las dos partes de una manera similar al intercambio de claves Diffie-Hellman basado en que el lado del cliente tiene la contraseña de usuario y el lado del servidor tiene un verificador criptográfico derivado de la contraseña. La clave pública compartida se deriva de dos números aleatorios, uno generado por el cliente y el otro generado por el servidor, que son exclusivos del intento de inicio de sesión. En los casos en los que se requieren comunicaciones cifradas y autenticación, el protocolo SRP es más seguro que el protocolo SSH alternativo y más rápido que utilizar el intercambio de claves Diffie-Hellman con mensajes firmados. También es independiente de terceros, a diferencia de Kerberos.
El protocolo SRP, versión 3, se describe en RFC 2945. La versión 6a de SRP también se utiliza para la autenticación de contraseña segura en SSL/TLS (en TLS-SRP) y otros estándares como EAP y SAML, y es parte de IEEE 1363.2. e ISO/CEI 11770-4.
Protocolo
En esta descripción del protocolo, versión 6, se utiliza la siguiente notación:
- q y N = 2q + 1 son elegidos tal que ambos son primos (que hace) q a Sophie Germain N un primo seguro). N debe ser lo suficientemente grande para que el modulo de logaritmos discretos N es infeasible.
- Todos los aritméticos se realizan en el anillo de los enteros modulo N, ZN{displaystyle scriptstyle mathbb {Z} _{N}. Esto significa que abajo gx debe leerse como gxmod N
- g es un generador del grupo multiplicador ZNAlternativa Alternativa {displaystyle scriptstyle mathbb {Z} {fn} {fn}}.
- H() es una función precipitada; por ejemplo, SHA-256.
- k es un parámetro derivado por ambas partes; en SRP-6, k = 3, mientras que en SRP-6a se deriva de N y g: k = H()N, g). Se utiliza para prevenir una adivinación de 2 por 1 cuando un atacante activo imita al servidor.
- s es una sal.
- I es un nombre de usuario identificativo.
- p es la contraseña del usuario.
- v es el verificador de contraseña del host, v = gx donde al mínimo x = H()s, p). As x sólo se calcula en el cliente es libre de elegir un algoritmo más fuerte. Una aplicación podría optar por utilizar x = H()s Silencio I Silencio p) sin afectar los pasos requeridos por el anfitrión. El RFC2945 estándar define x = H()s Silencio H ()I Silencio ":" p). Utilización I dentro x evita que un servidor malicioso pueda aprender si dos usuarios comparten la misma contraseña.
- A y B son al azar una vez claves efímeras del usuario y host respectivamente.
- Silencio (pipe) denota concatenación.
Todas las demás variables se definen en términos de estas.
Primero, para establecer una contraseña p con el servidor Steve, el cliente Carol elige una sal aleatoria s y calcula x = H(s, p), v = gx. Steve almacena v y s, indexados por I, como Carol&# Verificador de contraseñas de 39;s y salt. Carol no debe compartir x con nadie y debe borrarlo de forma segura en este paso, porque es equivalente a la contraseña de texto sin formato p. Este paso se completa antes de que se utilice el sistema como parte del registro de usuario con Steve. Tenga en cuenta que el salt s se comparte e intercambia para negociar una clave de sesión más adelante, de modo que cualquiera de las partes pueda elegir el valor, pero lo hace Carol para poder registrar el estilo I, s y v en una única solicitud de registro. La transmisión y autenticación de la solicitud de registro no está cubierta por el SRP.
Luego, para realizar una prueba de contraseña en una fecha posterior, se produce el siguiente protocolo de intercambio:
- Carol → Steve: generar valor aleatorio a; enviar I y A = ga
- Steve → Carol: generar valor aleatorio b; enviar s y B = kv + gb
- Ambos: u = H()A, B)
- Carol: SCarol =B − kgx)()a + ux) =kv + gb − kgx)()a + ux) =kgx − kgx + gb)(a + ux) =gb)()a + ux)
- Carol: KCarol = H()SCarol)
- Steve: SSteve. =Avu)b =gavu)b =ga()gx)u]b =ga + ux)b =gb)(a + ux)
- Steve: KSteve. = H()SSteve.) KCarol
Ahora las dos partes tienen una clave de sesión fuerte y compartida K. Para completar la autenticación, deben demostrarse mutuamente que sus claves coinciden. Una forma posible es la siguiente:
- Carol → Steve: M1 = H[H()NXOR H()gSilencio H()ISilencio s Silencio A Silencio B Silencio KCarol]. Steve verifica M1.
- Steve → Carol: M2 = H()A Silencio M1 Silencio KSteve.). Carol verifica M2.
Este método requiere adivinar más cosas del estado compartido para tener éxito en la suplantación que solo la clave. Si bien la mayor parte del estado adicional es público, se puede agregar información privada de manera segura a las entradas de la función hash, como la clave privada del servidor.
Alternativamente, en una prueba de solo contraseña, se puede omitir el cálculo de K y comprobar la S compartida con:
- Carol → Steve: M1 = H()A Silencio B Silencio SCarol). Steve verifica M1.
- Steve → Carol: M2 = H()A Silencio M1 Silencio SSteve.). Carol verifica M2.
Cuando se utiliza SRP para negociar una clave compartida K que se utilizará inmediatamente después de la negociación, es tentador omitir los pasos de verificación de M1 y M2. El servidor rechazará la primera solicitud del cliente que no pueda descifrar. Sin embargo, esto puede ser peligroso, como se demuestra en la sección Errores de implementación a continuación.
Las dos partes también emplean las siguientes salvaguardas:
- Carol abortará si recibe B 0 (modelo) N) o u = 0.
- Steve abortará si recibe A (modelo) N) = 0.
- Carol debe mostrar su prueba de K (o SPrimero. Si Steve detecta que la prueba de Carol es incorrecta, debe abortar sin mostrar su propia prueba de K (o S)
Código de ejemplo en Python
"Un ejemplo de autenticación SRPADVERTENCIA: No utilizar para propósitos criptográficos reales más allá de las pruebas.ADVERTENCIA: Este siguiente código pierde importantes salvaguardias. No comprueba A, B y U no son cero.basado en http://srp.stanford.edu/design.html"importación hashlibimportación al azar# Note: str converts as is, str([1,2,3,4]) will convert to "[1,2,3,4]"def H()*args) - No. int: ""Una función de hash de un solo sentido."" a = ":".Únase()str()a) para a dentro args) Regreso int()hashlib.sha256()a.código()"utf-8").Hexdigest(), 16)def cryptrand()n: int = 1024): Regreso al azar.SystemRandom().getrandbits()n) % N# Una gran prima segura (N = 2q+1, donde q es primo)# Todo aritmético se hace modulo N# (generado usando "openssl dhparam -text 1024")N = ""00:c0:37:c3:75:88:b4:32:98:87:e6:1c:2d:a3:32: 4b:1b:a4:b8:1a:63:f9:74:8f:ed:2d:8a:41:0c:2f: c2:1b:12:32:f0:d3:bf:a0:24:27:6c:fd:88:44:81: 97:aa:e4:86:a6:3b:fc:a7:b8:bf:77:54:df:b3:27: c7:20:1f:6f:d1:7f:d7:fd:74:15:8b:d3:1c:e7:72: c9:f5:f8:ab:58:45:48:a9:9a:75:9b:5a:2c:05:32: 16:2b:7b:62:18:e8:f1:42:bc:e2:c3:0d:77:84:68: 9a:48:3e:09:5e:70:16:18:43:79:13:a8:c3:9c:3d: d0:d4:ca:3c:50:0b:88:5f:e3"""N = int()".Únase()N.división().reemplazar()":", "), 16)g = 2 # Un modulo del generador Nk = H()N, g) # Parámetro multiplicador (k=3 en SRP-6)F = '#0x ' # Format specifierimpresión()"# H, N, g, y k son conocidos de antemano tanto al cliente como al servidor:")impresión()f'{}H =n{}N ==F}n{}g ==F}n{}k ==F}')impresión()"n0. tiendas de servidores (I, s, v) en su base de datos de contraseñas")# El servidor debe primero generar el verificador de contraseñaI = "persona" # Nombre de usuariop = "password1234" # Passwords = cryptrand()64) # Sal para el usuariox = H()s, I, p) # Clave privadav = pow()g, x, N) # Password verifierimpresión()f'{}I =n{}p =n{}s ==F}n{}x ==F}n{}v ==F}')# 0. servidores(I, s, v) en su base de datos de contraseña# I = 'persona '# p = 'password1234 '# s = 0x67bc8932cfd26a49# x = 0x98a4bce8dde877762a90222f1a1161eba9248590a47eb83aaa9e5bd7ecda5368d# v = 0xa7e2038e675d577ac0f318999cab67bba7ec2daf45d2d09f7911b78d2fc7f963cd0ac8f17851e0516f059e453672c3b70fcf5f6843180b271abdd01f552ccda7b♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪♪impresión()"n1. cliente envía el nombre de usuario I y el valor efímero público A al servidor")a = cryptrand()A = pow()g, a, N)impresión()f"{}I =n{}A ==F}") cliente-servidor (I, A)# 1. cliente envía el nombre de usuario I y el valor efímero público A al servidor# I = 'persona '# A = 0x678556a7e76581e051af656e8cee57ae46df43f1fce790f7750a3ec5308a85da4ec4051e5cb74d3e463685ee975a27474cf49035be67c931b56e793f23ea3524a♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪♪impresión()"n2. servidor envía sal del usuario y valor efímero público B al cliente")b = cryptrand()B = ()k * v + pow()g, b, N) % Nimpresión()f"{}s ==F}n{}B ==F}") # server-propiente (s, B)# 2. servidor envía sal del usuario y valor efímero público B al cliente# s = 0x67bc8932cfd26a49# B = 0xb615a0a5a♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪♪impresión()"n3. cliente y servidor calculan el parámetro scrambling aleatorio")u = H()A, B) # Parámetro aleatorioimpresión()f"{}u ==F}")# 3. cliente y servidor calculan el parámetro scrambling aleatorio# u = 0x796b07e354c04f672af8b76a46560655086355a9bbce11361f01b45d991c0c52♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪♪impresión()"n4. cliente compute sesión clave")x = H()s, I, p)S_c = pow()B - k * pow()g, x, N), a + u * x, N)K_c = H()S_c)impresión()f"{}S_c ==F}n{}K_c ==F}")# 4. cliente compute sesión clave# S/c = 0x699170aff6e9f08ed09a1dff432bf0605b8bcba05aadcaeea6657d06dbda4348e211d16c10ef4678585bcb2809a83c6285b6c19d97901274ddafd4075f90604c06baf036587# K_c = 0x43f8df6e1d2ba762948c8316db5bf03a7af49391742f5f51029630711c1671e♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪♪impresión()"n5. servidor compute sesión clave")S_S = pow()A * pow()v, u, N), b, N)K_S = H()S_S)impresión()f"{}S_S ==F}n{}K_S ==F}")# 5. servidor compute sesión clave# S/s = 0x699170aff6e9f08ed09a1dff432bf0605b8bcba05aadcaeea6657d06dbda4348e211d16c10ef4678585bcb2809a83c6285b6c19d97901274ddafd4075f90604c06b# K_s = 0x43f8df6e1d2ba762948c8316db5bf03a7af49391742f5f51029630711c1671e♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪♪impresión()"n6. cliente envía una prueba de clave de sesión al servidor")M_c = H()H()N) ^ H()g), H()I), s, A, B, K_c)impresión()f"{}M_c ==F}")# cliente-conservador (M_c); servidor verifica M_c# 6. cliente envía la prueba de clave de sesión al servidor# M_c = 0x75500df4ea36e06406ac1f8241429b8e90a8cba3adda3405c07f19ea3101e8♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪ ♪♪impresión()"n7. servidor envía una prueba de clave de sesión al cliente")M_S = H()A, M_c, K_S)impresión()f"{}M_S ==F}")# server-propiente (M_s); cliente verifica M_S# 7. servidor envía la prueba de clave de sesión al cliente# M_s = 0x182ed24d1ad2fb55d2268c46b42435d1ef02e0fc49f647c03dab8b2a48bd3d
Dificultades para la aplicación
Ataque de fuerza bruta fuera de línea con mensaje de primer servidor en ausencia de verificación de clave
Si el servidor envía un mensaje cifrado sin esperar la verificación del cliente, entonces un atacante puede montar un ataque de fuerza bruta fuera de línea similar al craqueo de hash. Esto puede suceder si el servidor envía un mensaje cifrado en el segundo paquete junto con salt y B o si se omite la verificación de la clave y el servidor (en lugar del cliente) envía el primer mensaje cifrado. Esto es tentador ya que después del primer paquete, el servidor tiene toda la información para calcular la clave compartida K.
El ataque es el siguiente:
- Carol → Steve: generar valor aleatorio a; enviar I y A = ga
- Steve: u = H()A, B); S=Avu; K=H()S)
- Steve: generar mensaje m y lo encripta para producir c=ENC(K,m)
- Steve → Carol: generar valor aleatorio b; enviar s, B = kv + gb y c
Carol no sabe x ni v. Pero dada cualquier contraseña p, ella puede calcular:
- xp = H(Sal. p)
- Sp =B - kgxp)()a + uxp)
- Kp = H()Sp)
Kp es la clave que Steve usaría si p fuera la contraseña esperada. Todos los valores necesarios para calcular Kp están controlados por Carol o son conocidos desde el primer paquete de Steve. Carol ahora puede intentar adivinar la contraseña, generar la clave correspondiente e intentar descifrar el mensaje cifrado c de Steve para verificar la clave. Como los mensajes de protocolo tienden a estar estructurados, se supone que es fácil identificar que c se descifró correctamente. Esto permite la recuperación fuera de línea de la contraseña.
Este ataque no habría sido posible si Steve hubiera esperado a que Carol demostrara que era capaz de calcular la clave correcta antes de enviar un mensaje cifrado. Este ataque no afecta las implementaciones adecuadas de SRP, ya que el atacante no podría pasar el paso de verificación de claves.
Fuerza bruta sin conexión basada en ataques sincronizados
En 2021, Daniel De Almeida Braga, Pierre-Alain Fouque y Mohamed Sabt publicaron PARASITE, un artículo en el que demuestran la explotación práctica de un ataque de sincronización en la red. Esto explota implementaciones no constantes de exponenciación modular de grandes números y afectó a OpenSSL en particular.
Implementaciones
- Variables SRP-6 Una biblioteca Java de primitivos criptográficos requeridos para implementar el protocolo SRP-6.
- Versión OpenSSL 1.0.1 o posterior.
- Botánico (la biblioteca de C++) contiene una implementación de SRP-6a
- TLS-SRP es un conjunto de ciphersuites para la seguridad de la capa de transporte que utiliza SRP.
- srp-client SRP-6a implementation in JavaScript (compatible con RFC 5054), open source, Mozilla Public License (MPL) licensed.
- La Biblioteca Crypto JavaScript incluye una implementación JavaScript del protocolo SRP, código abierto, licencia BSD.
- Gnu Crypto proporciona una implementación Java con licencia bajo la Licencia Pública General de GNU con la "excepción de biblioteca", que permite su uso como biblioteca junto con software no gratuito.
- La Legión del Castillo de Bouncy proporciona implementaciones Java y C# bajo la Licencia MIT.
- Nimbus SRP es una biblioteca de Java que proporciona un generador verificador, sesiones cliente y lado servidor. Incluye interfaces para clave de contraseña personalizada, rutinas de mensajes de evidencia cliente y servidor. Ninguna dependencia externa. Publicado bajo la licencia Apache 2.0.
- srplibcpp es una base de implementación C++ en MIRACL.
- DragonSRP es una implementación modular C+ actualmente funciona con OpenSSL.
- Json2Ldap proporciona autenticación SRP-6a a los servidores del directorio LDAP.
- csrp SRP-6a implementation in C.
- Crypt-SRP-6a implementation in Perl.
- pysrp SRP-6a implementation in Python (compatible con csrp).
- py3srp SRP-6a implementación en puro Python3.
- srptools Herramientas para implementar la autenticación Secure Remote Password (SRP) en Python. Bibliotecas compatibles verificadas.
- El sistema Cuentas del marco Meteor implementa SRP para la autenticación de contraseñas.
- srp-rb SRP-6a implementación en Ruby.
- falkmueller demo SRP-6a implementación del Stanford SRP Protocol Design en JavaScript y PHP bajo la licencia MIT.
- srp-6a-demo Aplicación SRP-6a en PHP y JavaScript.
- finobus-srp-js Aplicación SRP-6a en JavaScript. Viene con clases Java compatibles que utilizan Nimbus SRP una aplicación de demostración usando Spring Security. También hay una aplicación de demostración que realiza autenticación a un servidor PHP. Liberado bajo la Licencia Apache.
- Stanford JavaScript Crypto Library (SJCL) implementa SRP para intercambio clave.
- node-srp es un cliente JavaScript y servidor (node.js) aplicación de SRP.
- SRP6 para la implementación de C# y Java en C# y Java.
- ALOSRPAuth es una implementación Objetivo-C del SRP-6a.
- go-srp es una aplicación Go de SRP-6a.
- tsrp6a es una implementación TipoScript de SRP-6a.
- Cryptografía de IceNet Biblioteca Java para desarrollar aplicaciones basadas en la criptografía Spring Boot. Implementa SRP-6a. Bajo la Licencia Apache.
- SRP-6a in.NET implementation of SRP-6a
- Apple Homekit Apple Homekit utiliza SRP cuando se combina con los accesorios caseros "mart"
- Autenticación de correo Proton para cifrado de correo electrónico
- SRP es una aplicación Go de SRP, utilizada para autenticar usuarios en Posterity.
Historia
El proyecto SRP se inició en 1997. Dos enfoques diferentes para solucionar un agujero de seguridad en SRP-1 dieron como resultado SRP-2 y SRP-3. SRP-3 se publicó por primera vez en 1998 en una conferencia. RFC 2945, que describe SRP-3 con SHA1, se publicó en 2000. SRP-6, que corrige problemas "dos por uno" adivinanzas y mensajes para ordenar ataques, se publicó en 2002. SRP-6a apareció en la publicación oficial "libsrp" en la versión 2.1.0, de 2005. SRP-6a se encuentra en estándares como:
- ISO/IEC 11770-4:2006 "Key Agreement Mechanism 2" (llama el método "SRP-6, pero tiene el k cálculo de 6a)
- RFC 5054 TLS-SRP de 2007 (de nuevo denominado "SRP-6", pero corregido en erratum)
- IEEE Std 1363.2-2008 "DLAPKAS-SRP6" (de nuevo denominado "SRP-6")
IEEE 1363.2 también incluye una descripción de "SRP5", una variante que reemplaza el logaritmo discreto con una curva elíptica aportada por Yongge Wang en 2001. También describe SRP-3 como se encuentra en RFC 2945.