La guía paso a paso o código que hallarás en este post es la solución más eficiente y válida que encontramos a tu duda o dilema.
Solución:
Ninguna de las implementaciones que proporcione en su pregunta es del todo correcta y ninguna de las implementaciones que proporcione debe usarse tal cual. A continuación, discutiré los aspectos del cifrado basado en contraseña en Android.
Claves y hashes
Comenzaré a discutir el sistema basado en contraseñas con sales. La sal es un número generado aleatoriamente. No se “deduce”. La implementación 1 incluye un generateSalt()
método que genera un número aleatorio criptográficamente fuerte. Debido a que la sal es importante para la seguridad, debe mantenerse en secreto una vez que se genera, aunque solo debe generarse una vez. Si se trata de un sitio web, es relativamente fácil mantener el secreto de la sal, pero para las aplicaciones instaladas (para escritorio y dispositivos móviles), esto será mucho más difícil.
El método getHash()
devuelve un hash de la contraseña y la sal dadas, concatenado en un solo string. El algoritmo utilizado es SHA-512, que devuelve un hash de 512 bits. Este método devuelve un hash que es útil para verificar un stringintegridad, por lo que también podría usarse llamando getHash()
con solo una contraseña o solo un salt, ya que simplemente concatena ambos parámetros. Dado que este método no se utilizará en el sistema de cifrado basado en contraseña, no lo discutiré más.
El método getSecretKey()
, deriva un key a partir de una char
array de la contraseña y un salt codificado en hexadecimal, como se devuelve de generateSalt()
. El algoritmo utilizado es PBKDF1 (creo) de PKCS5 con SHA-256 como función hash, y devuelve 256 bits key. getSecretKey()
genera un key generando repetidamente hashes de la contraseña, sal y un contador (hasta el recuento de iteraciones dado en PBE_ITERATION_COUNT
, aquí 100) para aumentar el tiempo necesario para montar un ataque de fuerza bruta. La longitud de la sal debe ser al menos tan larga como la key generando, en este caso, al menos 256 bits. El recuento de iteraciones debe establecerse el mayor tiempo posible sin causar un retraso irrazonable. Para obtener más información sobre sales y recuentos de iteraciones en key derivación, consulte la sección 4 en RFC2898.
Sin embargo, la implementación en el PBE de Java es defectuosa si la contraseña contiene caracteres Unicode, es decir, aquellos que requieren más de 8 bits para ser representados. Como se indica en PBEKeySpec
, “el mecanismo PBE definido en PKCS # 5 mira solo los 8 bits de orden inferior de cada carácter”. Para solucionar este problema, puede intentar generar un hexadecimal string (que contendrá solo caracteres de 8 bits) de todos los caracteres de 16 bits en la contraseña antes de pasarla a PBEKeySpec
. Por ejemplo, “ABC” se convierte en “004100420043”. Tenga en cuenta también que PBEKeySpec “solicita la contraseña como un carácter array, para que se pueda sobrescribir [with clearPassword()
] cuando termine “. (Con respecto a” proteger cadenas en la memoria “, consulte esta pregunta). Sin embargo, no veo ningún problema con la representación de un sal como un código hexadecimal string.
Cifrado
Una vez key se genera, podemos usarlo para cifrar y descifrar texto.
En la implementación 1, el algoritmo de cifrado utilizado es AES/CBC/PKCS5Padding
, es decir, AES en el modo de cifrado Cipher Block Chaining (CBC), con relleno definido en PKCS # 5. (Otros modos de cifrado AES incluyen el modo de contador (CTR), el modo de libro de códigos electrónico (ECB) y el modo de contador de Galois (GCM). Otra pregunta sobre Stack Overflow contiene respuestas que discuten en detalle los diversos modos de cifrado AES y los recomendados para usar. Tenga en cuenta también que hay varios ataques al cifrado en modo CBC, algunos de los cuales se mencionan en RFC 7457.)
Tenga en cuenta que debe utilizar un modo de cifrado que también compruebe la integridad de los datos cifrados (p. Ej., cifrado autenticado con datos asociados, AEAD, descrito en RFC 5116). Sin embargo, AES/CBC/PKCS5Padding
no proporciona verificación de integridad, por lo que no se recomienda solo. Para propósitos de AEAD, usar un secreto que sea al menos dos veces más largo que un cifrado normal key se recomienda, para evitar relacionados key ataques: la primera mitad sirve como cifrado key, y la segunda mitad sirve como key para la verificación de integridad. (Es decir, en este caso, genere un único secreto a partir de una contraseña y sal, y divida ese secreto en dos).
Implementación de Java
Las diversas funciones de la implementación 1 utilizan un proveedor específico, a saber, “BC”, para sus algoritmos. Sin embargo, en general, no se recomienda solicitar proveedores específicos, ya que no todos los proveedores están disponibles en todas las implementaciones de Java, ya sea por falta de soporte, para evitar la duplicación de código o por otras razones. Este consejo se ha vuelto especialmente importante desde el lanzamiento de la vista previa de Android P a principios de 2018, porque algunas funciones del proveedor “BC” han quedado obsoletas allí; consulte el artículo “Cambios en la criptografía en Android P” en el Blog de desarrolladores de Android. Consulte también la Introducción a los proveedores de Oracle.
Por lo tanto, PROVIDER
no debería existir y el string -BC
debe ser eliminado de PBE_ALGORITHM
. La implementación 2 es correcta a este respecto.
Es inapropiado que un método capture todas las excepciones, sino que maneje solo las excepciones que pueda. Las implementaciones dadas en su pregunta pueden generar una variedad de excepciones comprobadas. Un método puede optar por envolver solo aquellas excepciones marcadas con CryptoException, o especificar esas excepciones marcadas en el throws
cláusula. Por conveniencia, encapsular la excepción original con CryptoException puede ser apropiado aquí, ya que hay potencialmente muchas excepciones marcadas que las clases pueden lanzar.
SecureRandom
en Android
Como se detalla en el artículo “Algunos pensamientos seguros al azar”, en el Blog de desarrolladores de Android, la implementación de java.security.SecureRandom
en las versiones de Android anteriores a 2013 tiene una falla que reduce la fuerza de los números aleatorios que entrega. Esta falla se puede mitigar como se describe en ese artículo.
# 2 nunca debe usarse ya que solo usa “AES” (lo que significa cifrado en modo ECB en texto, un gran no-no) para el cifrado. Solo hablaré del # 1.
La primera implementación parece adherirse a las mejores prácticas para el cifrado. Las constantes son generalmente correctas, aunque tanto el tamaño de la sal como el número de iteraciones para realizar PBE están en el lado corto. Además, parece ser para AES-256 ya que el PBE key La generación utiliza 256 como valor codificado de forma rígida (una pena después de todas esas constantes). Utiliza CBC y PKCS5Padding, que es al menos lo que cabría esperar.
Falta por completo cualquier protección de autenticación / integridad, por lo que un atacante puede cambiar el texto cifrado. Esto significa que los ataques de relleno de Oracle son posibles en un modelo cliente / servidor. También significa que un atacante puede intentar cambiar los datos cifrados. Esto probablemente dará como resultado algún error en algún lugar debido a que la aplicación no acepta el relleno o el contenido, pero esa no es una situación en la que desea estar.
El manejo de excepciones y la validación de entrada podrían mejorarse, la captura de excepciones siempre es incorrecta en mi libro. Además, la clase implementa ICrypt, que no sé. Sé que tener solo métodos sin efectos secundarios en una clase es un poco extraño. Normalmente, harías esos static. No hay almacenamiento en búfer de instancias de Cipher, etc., por lo que cada objeto requerido se crea ad-nauseum. Sin embargo, puede eliminar de forma segura ICrypto de la definición que parece, en ese caso también podría refactorizar el código para static métodos (o reescribirlo para que esté más orientado a objetos, tú eliges).
El problema es que cualquier contenedor siempre hace suposiciones sobre el caso de uso. Decir que un envoltorio es correcto o incorrecto es, por tanto, una tontería. Es por eso que siempre trato de evitar generar clases contenedoras. Pero al menos no parece explícitamente incorrecto.
Aquí tienes las reseñas y puntuaciones
Si estás contento con lo expuesto, tienes la libertad de dejar una división acerca de qué le añadirías a este tutorial.