Created
August 5, 2023 11:19
-
-
Save milaabl/268b678c7b9793a11e9f636d3ee3ba70 to your computer and use it in GitHub Desktop.
E2E RSA + AES encryption system (symmetric+asymmetric encryption for sensitive data transfers) | React, CryptoJS & Shell .sh (OpenSSL)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import CryptoJS from 'crypto-js'; | |
import './app.css'; | |
import FileInput from "./components/FileInput/FileInput"; | |
import { FC, useEffect, useState } from "react"; | |
const getAESEncrypted = (secret: string, password: string) => { | |
const encrypted = CryptoJS.AES.encrypt(secret, password); | |
return encrypted.toString(); | |
} | |
const fromBase64 = (base64String : string) => Uint8Array.from(atob(base64String), c => c.charCodeAt(0)); | |
const getPkciDer = (pkciPem : string) => { | |
console.log(pkciPem); | |
const pkciPemHeader = "-----BEGIN PUBLIC KEY-----"; | |
const pkciPemFooter = "-----END PUBLIC KEY-----"; | |
console.log(pkciPem); | |
pkciPem = pkciPem.substring(pkciPemHeader.length, pkciPem.length - pkciPemFooter.length); | |
console.log(pkciPem); | |
return fromBase64(pkciPem); | |
} | |
const importPublicKey = async (pkciPem : string) => { | |
return await window.crypto.subtle.importKey( | |
"spki", | |
getPkciDer(pkciPem), | |
{ | |
name: "RSA-OAEP", | |
hash: "SHA-1", | |
}, | |
true, | |
["encrypt"] | |
); | |
} | |
const encryptRSA = async (key : CryptoKey, text : string) => { | |
const enc = new TextEncoder(); | |
const encrypted = await window.crypto.subtle.encrypt( | |
{ | |
name: "RSA-OAEP" | |
}, | |
key, | |
enc.encode(text) | |
); | |
return encrypted; | |
} | |
const ab2str = (buf : ArrayBuffer) => { | |
//@ts-ignore | |
return String.fromCharCode.apply(null, new Uint8Array(buf)); | |
} | |
const getRSAEncrypted = async (text : string) => { | |
if (!process.env.REACT_APP_PUBLIC_RSA_KEY) { | |
throw new Error('No public key provided in the .env'); | |
} | |
const key = | |
await importPublicKey(process.env.REACT_APP_PUBLIC_RSA_KEY); | |
const encrypted = await encryptRSA(key, text); | |
download(createTextFile(window.btoa(ab2str(encrypted))), 'cipher_to_retrieve_aes_password.txt'); | |
} | |
const dec2hex = (dec: number): string => { | |
return dec.toString(16).padStart(2, "0"); | |
}; | |
const generateId = (len?: number): string => { | |
let arr = new Uint8Array((len || 32) / 2); | |
window.crypto.getRandomValues(arr); | |
return Array.from(arr, dec2hex).join(""); | |
}; | |
const download = (url: string, filename: string): void => { | |
let elm = document.createElement("a"); | |
elm.href = url; | |
elm.setAttribute("download", filename); | |
elm.click(); | |
document.body.append(elm); | |
}; | |
const encrypt = async (fileBase64: string): Promise<string> => { | |
const key = generateId(); | |
await getRSAEncrypted(key); | |
const aesEncryptedPayload = await getAESEncrypted(fileBase64, key); | |
download(createTextFile(aesEncryptedPayload), 'ciphered_file_contents.txt'); | |
return aesEncryptedPayload; | |
}; | |
const createTextFile = (text : string) : string => { | |
const data = new Blob([text], {type: 'text/plain'}); | |
const textFile = window.URL.createObjectURL(data); | |
return textFile; | |
}; | |
const App: FC = () => { | |
const [uploadedFileName, setUploadedFileName] = useState<string>(); | |
const [uploadedFileBase64, setUploadedFileBase64] = useState<string>(); | |
useEffect(() => { | |
if (!(uploadedFileBase64 && uploadedFileName)) return; | |
(async function () { | |
await encrypt(uploadedFileBase64.split(",")[1]); | |
})(); | |
}, [uploadedFileName, uploadedFileBase64]); | |
return ( | |
<main> | |
<FileInput | |
setUploadedFileName={setUploadedFileName} | |
setUploadedFileBase64={setUploadedFileBase64} | |
/> | |
</main> | |
); | |
}; | |
export default App; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
base64 --decode cipher_to_retrieve_aes_password.txt > cipher_to_retrieve_aes_password_binary.txt | |
openssl rsautl -decrypt -oaep -inkey private_key.pem -in cipher_to_retrieve_aes_password_binary.txt -out key_to_decrypt_aes.txt | |
RANDOM_KEY=$(cat key_to_decrypt_aes.txt) | |
base64 --decode ciphered_file_contents.txt > ciphered_file_contents_base64_decoded.txt | |
openssl enc -d -aes-256-cbc -in ciphered_file_contents_base64_decoded.txt -k ${RANDOM_KEY} -md MD5 -out original_file_contents.txt | |
base64 --decode original_file_contents.txt > original_file_contents_base64_decoded.txt | |
read |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { FileInputProps } from "./FileInput.types"; | |
import { useRef, FC } from "react"; | |
const fileToBase64 = (file: File): Promise<string> => { | |
return new Promise<string>((resolve, reject) => { | |
const reader = new FileReader(); | |
reader.readAsDataURL(file); | |
reader.onload = () => resolve(reader.result?.toString() || ""); | |
reader.onerror = (error) => reject(error); | |
}); | |
}; | |
const FileInput: FC<FileInputProps> = ({ | |
setUploadedFileName, | |
setUploadedFileBase64, | |
}) => { | |
const inputElement = useRef<HTMLInputElement>(null); | |
return ( | |
<section> | |
<input | |
ref={inputElement} | |
onChange={async (e) => { | |
if (!e.target?.files?.[0]) { | |
return; | |
} | |
const _fileSizeInMb = ( | |
e.target.files[0].size / | |
(1024 * 1024) | |
).toFixed(2); | |
if (+_fileSizeInMb > 10) { | |
alert("File is larger than 10 MB"); | |
e.target.value = ""; | |
if (!/safari/i.test(navigator.userAgent)) { | |
e.target.type = ""; | |
e.target.type = "file"; | |
} | |
return; | |
} | |
setUploadedFileName(e.target.files[0].name); | |
setUploadedFileBase64(await fileToBase64(e.target.files[0])); | |
}} | |
type="file" | |
/> | |
</section> | |
); | |
}; | |
export default FileInput; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment