functions - El cifrado y descifrado entre Java y Javascript no funcionarán
string javascript (3)
El link que se ha dado en la sección de comentarios resuelve parcialmente el problema. Cifra y descifra la plataforma cruzada, pero es bastante lento debido a PBKDF2 con hash SHA256. No puedo encontrar una manera de usar solo la parte AES y no la parte PKBDF2.
El propósito de PBKDF2 es convertir una contraseña elegida por el usuario (que normalmente es una cadena de texto de longitud variable, y rara vez tiene más de unas pocas docenas de bits de entropía efectiva *) en una clave AES (que debe ser una cadena binaria de exactamente 128, 192 o 256 bits, dependiendo de la variante de AES utilizada, y efectivamente debería tener una entropía completa ** si desea que el cifrado sea tan fuerte como debe ser).
Para hacer eso, tiene que ser lento; la única forma de hacer que adivinar una contraseña con, digamos, 30 bits de entropía sea tan difícil como adivinar una clave AES con 128 bits de entropía sería hacer que el proceso de conversión de una contraseña en una clave demorara tanto tiempo como 2 128 - 30 = 2 98 cifrados AES. Por supuesto, eso no es realmente posible, por lo que en la práctica las personas tienden a aplicar solo unos pocos miles (≈ 2 10 ) a unos pocos miles de millones (≈ 2 30 ) iteraciones de PBKDF2 y esperan que sea suficiente . Lo que podría ser, si su cuenta de iteraciones está más cerca del extremo superior del rango, si el usuario es lo suficientemente inteligente y lo suficientemente motivado para elegir una contraseña razonablemente buena (por ejemplo, una frase de contraseña de Diceware azar en lugar de abc123
o pa$$w0rd
) Para empezar, y si tu adversario no es la NSA.
De todos modos, el punto de todo esto es que, si quieres usar un cifrado basado en contraseña , entonces tienes que usar PBKDF2 (o something similar ). Sin embargo, eso no significa necesariamente que tenga que ejecutar la función de derivación de clave lenta cada vez que cifre o descifre algo. De hecho, si sabe que necesitará cifrar o descifrar varios archivos con la misma contraseña, es mucho más eficiente obtener la clave AES de la contraseña una vez, y luego almacenar la clave AES en la memoria todo el tiempo que necesite. eso. (Hacer eso de forma segura tampoco es trivial, pero muchas bibliotecas criptográficas lo manejarán al menos razonablemente bien si utiliza sus objetos de clave incorporados para almacenar las claves, y en cualquier caso es poco probable que sea el enlace más débil en la seguridad de su aplicación .)
La otra opción, por supuesto, es no usar contraseñas en absoluto, sino simplemente generar una clave AES (pseudo) aleatoria (utilizando un generador de cadenas de bits aleatorios criptográficamente seguro) y almacenar en todos los dispositivos que necesitan acceso a ella. Una vez más, por supuesto, la parte difícil aquí es almacenar la clave de forma segura.
En particular, si está haciendo criptografía en el lado del cliente con JavaScript en el navegador, simplemente incrustar la clave en el código JS (o en cualquier otro lugar de la página) lo expondrá a cualquier persona con acceso a la consola de desarrollo del navegador (y también puede dar lugar a que la clave quede en la memoria caché del disco del navegador, por ejemplo. Y, por supuesto, debería estar usando HTTPS, ya que de lo contrario, cualquier persona, por ejemplo, al escuchar la conexión WiFi pública del cliente, también podría tomar una copia de la clave.
PD. Puede notar que en realidad no he incluido ningún código que muestre cómo hacer el cifrado AES simple con PBKDF2 arriba. No lo he hecho por dos razones:
Si no entiende las primitivas criptográficas con las que está trabajando lo suficientemente bien como para, por ejemplo, separar la derivación de la clave del cifrado, entonces no debería escribir código criptográfico (como algo más que un ejercicio de juguete) de todos modos.
Eso puede sonar duro, pero es la realidad, a diferencia de muchos otros subcampos de la programación, donde puede tomar un fragmento de código que no comprende y modificar hasta que funcione, con criptografía (y con código relacionado con la seguridad en en general, necesita saber lo que está haciendo y hacerlo bien la primera vez.
Con otros tipos de código, algo que parece funcionar la mayor parte del tiempo a menudo es lo suficientemente bueno, y puede corregir cualquier error restante a medida que surjan. Con el código criptográfico, el código de buggy puede ser completamente inseguro al mismo tiempo que parece que funciona perfectamente, y no descubrirás que está roto hasta que alguien lo rompa y robe todos los datos que trabajaste tan duro para mantener en secreto. O posiblemente solo mucho después de que eso ya haya sucedido.
En cualquier caso, link (<disclaimer> y que no he revisado en detalle </disclaimer>) ya incluye un conjunto de métodos que toman claves AES binarias y no usan PBKDF2. Específicamente, esos son los métodos
encryptString()
ydecryptString()
a diferencia deencryptString()
ydecryptString()
, que utilizan PBKDF2).Tenga en cuenta que, al llamar a
encrypt()
y / odecrypt()
, deberá proporcionar la clave AES en el formato esperado por la implementación. En general, eso puede depender de la biblioteca criptográfica subyacente utilizada por el código. La implementación de Java , por ejemplo, parece estar esperando una matriz debyte[]
= 16 elementosbyte[]
. (Sería bueno si también pudiera tomar directamente un objetoSecretKeySpec
, pero no lo hace). La implementación de JS WebCrypto parece querer unUint8Array
16 bytes enUint8Array
lugar. apparently , eso también está bien para la implementación de node.js , aunque también puede aceptar unBuffer
.
*) Es decir, un programa de descifrado de contraseñas decente, basado en un profundo conocimiento de los métodos y hábitos comunes de búsqueda de contraseñas, rara vez requerirá más de unos pocos miles de millones (≈ 2 30 ) o trillones (≈ 2 40 ) intentos Adivinar la mayoría de las contraseñas elegidas por los humanos. De hecho, dependiendo de cuán perezosos o inexpertos sean sus usuarios, incluso probar las pocas miles de contraseñas más comunes puede ser bastante efectivo.
**) Es decir, adivinar la clave no debería ser más fácil que adivinar una cadena de bits elegida al azar de la misma longitud.
EDITAR 1
En el método decryptFile, la parte de descifrado no generará nada ...
let decrypted = CryptoJS.AES.decrypt(e.target.result, CryptoJS.enc.Utf8.parse(key), {
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
EDIT 2 El link que se ha dado en la sección de comentarios resuelve parcialmente el problema. Cifra y descifra la plataforma cruzada, pero es bastante lento debido a PBKDF2 con hash SHA256. No puedo encontrar una manera de usar solo la parte AES y no la parte PKBDF2.
TEXTO ORIGINAL
Estoy usando la misma clave y IV tanto para la versión de Java como para la de Javascript. No puedo descifrar un archivo en Javascript que ha sido cifrado en Java, ni puedo descifrar un archivo en Java que ha sido cifrado en Javascript. Necesito que esos dos sean compatibles entre sí, pero no puedo entender cómo estoy intentando descifrar un archivo en Javascript que estaba cifrado previamente en Java. He implementado con éxito el descifrado y el cifrado de texto entre los dos, pero cuando me gustaría, por ejemplo, descifrar un archivo que está cifrado en Java, simplemente no funciona.
Cifrar / descifrar un archivo en Java:
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Test {
private SecretKey secretKey;
private IvParameterSpec ivParameterSpec;
private String key = "ThisIsMyGreatKey";
private byte[] ivKey = "ABCDEFGHabcdefgh".getBytes();
public static void main(String[] args) {
try {
new Test().run();
} catch (Exception e) {
e.printStackTrace();
}
}
private void run() {
ivParameterSpec = new IvParameterSpec(ivKey);
secretKey = new SecretKeySpec(key.getBytes(), "AES");
encryptOrDecryptFile(Cipher.ENCRYPT_MODE,
new File("src/cactus.jpg"), new File("src/cactus-encrypted.jpg"));
}
private void encryptOrDecryptFile(int mode, File inputFile, File outputFile) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(mode, secretKey, ivParameterSpec);
// Read input
byte[] input = new byte[(int) inputFile.length()];
FileInputStream inputStream = new FileInputStream(inputFile);
inputStream.read(input);
// Encrypt and write to output
byte[] output = cipher.doFinal(input);
FileOutputStream outputStream = new FileOutputStream(outputFile);
outputStream.write(output);
inputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Cifrar / descifrar en Javascript
<input type="file" id="file-input" onchange="handleFile(this)">
<button onclick="useEncryptionForFile()" id="encrypt-file">Encrypt File</button>
<button onclick="useDecryptionForFile()" id="decrypt-file">Decrypt File</button>
<textarea id="output"></textarea>
<img id="example">
<script>
let key = "ThisIsMyGreatKey";
let iv = "ABCDEFGHabcdefgh";
let useEncryption, useDecryption;
let input = document.getElementById("file-input");
let output = document.getElementById("output");
let example = document.getElementById("example");
function handleFile(element) {
if (element.files && element.files[0]) {
let file = element.files[0];
if (useDecryption) {
decryptFile(file);
} else {
encryptFile(file);
}
}
}
function encryptFile(file) {
let reader = new FileReader();
reader.onload = function (e) {
let encrypted = CryptoJS.AES.encrypt(e.target.result, CryptoJS.enc.Utf8.parse(key), {
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
output.textContent = encrypted;
let a = document.createElement("a");
a.setAttribute(''href'', ''data:application/octet-stream,'' + encrypted);
a.setAttribute(''download'', file.name + ''.encrypted'');
a.click();
};
reader.readAsDataURL(file);
}
function decryptFile(file) {
let reader = new FileReader();
reader.onload = function (e) {
let decrypted = CryptoJS.AES.decrypt(e.target.result, CryptoJS.enc.Utf8.parse(key), {
iv: CryptoJS.enc.Utf8.parse(iv),
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
// Decrypted is emtpy
output.textContent = decrypted;
// Desperate try to get something working
example.src = "data:image/png;base64," + btoa(decrypted);
let a = document.createElement("a");
a.setAttribute(''href'', decrypted);
a.setAttribute(''download'', file.name.replace(''encrypted'', ''decrypted''));
a.click();
};
reader.readAsText(file);
}
function useEncryptionForFile() {
document.getElementById("encrypt-file").style.backgroundColor = "#757575";
document.getElementById("decrypt-file").style.backgroundColor = "#FFFFFF";
useEncryption = true;
useDecryption = false;
}
function useDecryptionForFile() {
document.getElementById("encrypt-file").style.backgroundColor = "#FFFFFF";
document.getElementById("decrypt-file").style.backgroundColor = "#757575";
useDecryption = true;
useEncryption = false;
}
</script>
También hice un Fiddle en caso de que quisiera más :), y la fuente de Java se puede descargar aquí .
En la fuente de Java utilicé un archivo cactus.jpg, pero se podría usar cualquier archivo :). El cactus se puede encontrar here .
¿Cómo puedo descifrar el archivo que ha sido cifrado en Java? He intentado convertir el contenido de blobs en String, recuperar los datos como ArrayBuffer y convertirlos en String, recibirlos como texto y pasarlos al método de descifrado, pero nada parece funcionar.
La biblioteca que estoy usando en Javascript es CryptoJS , y en Java las bibliotecas estándar de Crypto.
Encontré otras preguntas similares ( 1 , 2 ). Sin embargo, creo que difieren demasiado, ya que la respuesta de esas preguntas no se refiere a este problema, sino a un pequeño error.
Si he olvidado algún dato, por favor dímelo.
El problema es que interpretas el resultado del descifrado como una cadena UTF8. No es así como funciona. Los archivos son solo bytes arbitrarios, no necesariamente forman una cadena UTF8. El resultado de su descifrado es correcto si no intenta interpretarlo como UTF8.
En primer lugar, intente enviar texto cifrado simple de java a javascript o viceversa y pruebe si el código está funcionando.
Si el código funciona para Texto simple, es decir, puede enviar una Cadena encriptada desde Java y desencriptarla con éxito en JavaScript o viceversa, entonces lo que puede hacer es Codificar en Base64 los bytes / archivos encriptados y luego transferir el texto y luego decodificar y descifrarlo en el otro extremo.
Si el código no funciona para texto simple, puede intentar cifrar un texto simple en javascript y java de forma independiente y verificar si el resultado es el mismo. Si no, hay una discrepancia en la lógica de cifrado / descifrado entre java y javascript.
EDITAR:
Como mencionó que el código funciona para String, a continuación se muestra un ejemplo para convertir un archivo en String Base64 usando la biblioteca de códecs comunes de Apache en Java.
private static String encodeFileToBase64Binary(String fileName) throws IOException {
File file = new File(fileName);
byte[] encoded = Base64.encodeBase64(FileUtils.readFileToByteArray(file));
return new String(encoded, StandardCharsets.US_ASCII);
}
Ahora cifras esta cadena y la envías a javascript. En javascript primero descifre la cadena y luego conviértala en un objeto de archivo.
P.ej.
function base64toFile(encodedstring,filename,mimeType){
return new File([encodedstring.arrayBuffer()],filename, {type:mimeType});
}
//Usage example:
base64toFile(''aGVsbG8gd29ybGQ='', ''hello.txt'', ''text/plain'');