mostly works now, except for decrypting the file, but not getting any helpful error messages from SubtleCrypto to fix this
This commit is contained in:
		
							parent
							
								
									917797c9be
								
							
						
					
					
						commit
						dfcacf7bbf
					
				
					 5 changed files with 78 additions and 56 deletions
				
			
		| 
						 | 
				
			
			@ -2,10 +2,18 @@
 | 
			
		|||
  import EncryptButton from './lib/EncryptButton.svelte'
 | 
			
		||||
  import PasswordButton from './lib/PasswordButton.svelte';
 | 
			
		||||
  import DecryptButton from './lib/DecryptButton.svelte';
 | 
			
		||||
  import { errorMessage } from './lib/generalStore';
 | 
			
		||||
 | 
			
		||||
  let errorMessageContent
 | 
			
		||||
  errorMessage.subscribe( value => {
 | 
			
		||||
    errorMessageContent = value;
 | 
			
		||||
  });
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<main>
 | 
			
		||||
  <h1>Encrypt and decrypt files</h1>
 | 
			
		||||
  {errorMessageContent}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <div class="container">
 | 
			
		||||
      <EncryptButton />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import { onMount } from 'svelte';
 | 
			
		||||
    import { escape } from 'svelte/internal';
 | 
			
		||||
  import { decrypt, encrypt } from './cryptlib';
 | 
			
		||||
  import { decryptFileContent } from './cryptlib';
 | 
			
		||||
  let filename: string = "";
 | 
			
		||||
  let originalFilename: string;
 | 
			
		||||
  
 | 
			
		||||
| 
						 | 
				
			
			@ -44,23 +44,18 @@
 | 
			
		|||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    readFileContent(file).then((fileContent) => {
 | 
			
		||||
      decrypt(atob(fileContent)).then(decryptedContent => {
 | 
			
		||||
        console.log("Decrypted Base64: ", decryptedContent)
 | 
			
		||||
        console.log(decodeURIComponent(decryptedContent));
 | 
			
		||||
        const decodedData = decodeURIComponent(escape(atob((decryptedContent))));
 | 
			
		||||
        const outputArray = new Uint8Array(decodedData.length)
 | 
			
		||||
        for (let i = 0; i < decodedData.length; i++) {
 | 
			
		||||
          outputArray[i] = decodedData.charCodeAt(i); // Populate the array with the decoded data
 | 
			
		||||
        }
 | 
			
		||||
        const outputFile = new Blob([outputArray], { type: 'application/octet-stream'});
 | 
			
		||||
        let url = URL.createObjectURL(outputFile);
 | 
			
		||||
        let a = document.createElement("a");
 | 
			
		||||
        a.href = url;
 | 
			
		||||
        a.download = originalFilename;
 | 
			
		||||
  		  document.body.appendChild(a);
 | 
			
		||||
  		  a.click();
 | 
			
		||||
  		  document.body.removeChild(a);
 | 
			
		||||
      let outputFile: Blob;
 | 
			
		||||
      decryptFileContent(fileContent).then((outputArray) => { 
 | 
			
		||||
      console.log(outputArray)
 | 
			
		||||
      outputFile = new Blob([outputArray], { type: 'application/octet-stream'});
 | 
			
		||||
      });
 | 
			
		||||
      let url = URL.createObjectURL(outputFile);
 | 
			
		||||
      let a = document.createElement("a");
 | 
			
		||||
      a.href = url;
 | 
			
		||||
      a.download = originalFilename;
 | 
			
		||||
  		document.body.appendChild(a);
 | 
			
		||||
  		a.click();
 | 
			
		||||
  		document.body.removeChild(a);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
  import { onMount } from 'svelte';
 | 
			
		||||
  import { encrypt } from './cryptlib';
 | 
			
		||||
  let filename: string;
 | 
			
		||||
  import { encryptFile } from './cryptlib';
 | 
			
		||||
  let filename: string = "";
 | 
			
		||||
  
 | 
			
		||||
  function convertToBase64(blob: File): Promise<string> {
 | 
			
		||||
    return new Promise((resolve, reject) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -17,7 +17,16 @@
 | 
			
		|||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  function downloadAsFile(string) {
 | 
			
		||||
      let encrypted_file: Blob = new Blob([string], {type: "text/plain"});
 | 
			
		||||
      let url = URL.createObjectURL(encrypted_file);
 | 
			
		||||
      let a = document.createElement("a");
 | 
			
		||||
      a.href = url;
 | 
			
		||||
      a.download = filename + ".crypt";
 | 
			
		||||
		  document.body.appendChild(a);
 | 
			
		||||
		  a.click();
 | 
			
		||||
		  document.body.removeChild(a);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
  let file: File | undefined;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -34,22 +43,12 @@
 | 
			
		|||
    }
 | 
			
		||||
    filename = file.name;
 | 
			
		||||
    console.log("Uploaded ", filename)
 | 
			
		||||
    convertToBase64(file).then((base64) => {
 | 
			
		||||
      encrypt(base64).then((encrypted_content => {
 | 
			
		||||
      console.log(encrypted_content)
 | 
			
		||||
      let encrypted_b64: string = btoa(unescape(encodeURIComponent(encrypted_content)));
 | 
			
		||||
      let encrypted_file: Blob = new Blob([encrypted_b64], {type: "text/plain"});
 | 
			
		||||
      let url = URL.createObjectURL(encrypted_file);
 | 
			
		||||
      let a = document.createElement("a");
 | 
			
		||||
      a.href = url;
 | 
			
		||||
      a.download = filename + ".crypt";
 | 
			
		||||
		  document.body.appendChild(a);
 | 
			
		||||
		  a.click();
 | 
			
		||||
		  document.body.removeChild(a);
 | 
			
		||||
      }));
 | 
			
		||||
    encryptFile(file).then(base64String => {
 | 
			
		||||
      downloadAsFile(base64String)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
</script>
 | 
			
		||||
<style>
 | 
			
		||||
  .upload-button {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,9 +1,10 @@
 | 
			
		|||
//import crypto from 'crypto'
 | 
			
		||||
import { globalPassword } from './pwgen';
 | 
			
		||||
import { errorMessage } from './generalStore';
 | 
			
		||||
let password: string;
 | 
			
		||||
let salt;
 | 
			
		||||
let ciphertext;
 | 
			
		||||
let iv;
 | 
			
		||||
let salt: Uint8Array;
 | 
			
		||||
let ciphertext: BufferSource;
 | 
			
		||||
let iv: Uint8Array;
 | 
			
		||||
globalPassword.subscribe(value => {
 | 
			
		||||
  password = value;
 | 
			
		||||
})
 | 
			
		||||
| 
						 | 
				
			
			@ -33,12 +34,14 @@ function getKeyMaterial() {
 | 
			
		|||
/*
 | 
			
		||||
Given some key material and some random salt
 | 
			
		||||
derive an AES-GCM key using PBKDF2.
 | 
			
		||||
Using the Password itself as salt isn't really a great idea, but this is not a hyper secure implementation yet
 | 
			
		||||
*/
 | 
			
		||||
function getKey(keyMaterial, salt) {
 | 
			
		||||
function getKey(keyMaterial: CryptoKey) {
 | 
			
		||||
  let enc = new TextEncoder();
 | 
			
		||||
  return window.crypto.subtle.deriveKey(
 | 
			
		||||
    {
 | 
			
		||||
      "name": "PBKDF2",
 | 
			
		||||
      salt: salt, 
 | 
			
		||||
      salt: enc.encode(password), 
 | 
			
		||||
      "iterations": 100000,
 | 
			
		||||
      "hash": "SHA-256"
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -55,13 +58,12 @@ to encrypt the message.
 | 
			
		|||
Update the "ciphertextValue" box with a representation of part of
 | 
			
		||||
the ciphertext.
 | 
			
		||||
*/
 | 
			
		||||
export async function encrypt(string: string) {
 | 
			
		||||
  let dec = new TextDecoder();
 | 
			
		||||
export async function encryptFile(file: File) {
 | 
			
		||||
  let keyMaterial = await getKeyMaterial();
 | 
			
		||||
  salt = window.crypto.getRandomValues(new Uint8Array(16));
 | 
			
		||||
  let key = await getKey(keyMaterial, salt);
 | 
			
		||||
  let key = await getKey(keyMaterial);
 | 
			
		||||
  iv = window.crypto.getRandomValues(new Uint8Array(12));
 | 
			
		||||
  let encoded = getStringEncoding(string);
 | 
			
		||||
  const fileReader = new FileReader();
 | 
			
		||||
  let byteArray = await file.arrayBuffer();
 | 
			
		||||
 | 
			
		||||
  ciphertext = await window.crypto.subtle.encrypt(
 | 
			
		||||
    {
 | 
			
		||||
| 
						 | 
				
			
			@ -69,11 +71,10 @@ export async function encrypt(string: string) {
 | 
			
		|||
      iv: iv
 | 
			
		||||
    },
 | 
			
		||||
    key,
 | 
			
		||||
    encoded
 | 
			
		||||
    byteArray
 | 
			
		||||
  );
 | 
			
		||||
  let decodedCiphertext = dec.decode(ciphertext);
 | 
			
		||||
  return decodedCiphertext
 | 
			
		||||
 | 
			
		||||
  const base64Encoded = btoa(String.fromCharCode(... new Uint8Array(ciphertext)));
 | 
			
		||||
  return base64Encoded
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
| 
						 | 
				
			
			@ -84,25 +85,39 @@ update the "decryptedValue" box with the decrypted value.
 | 
			
		|||
If there was an error decrypting,
 | 
			
		||||
update the "decryptedValue" box with an error message.
 | 
			
		||||
*/
 | 
			
		||||
export async function decrypt(encryptedContent) {
 | 
			
		||||
export async function decryptFileContent(base64Encoded: string) {
 | 
			
		||||
  //prepare cryptography
 | 
			
		||||
  let keyMaterial = await getKeyMaterial();
 | 
			
		||||
  let key = await getKey(keyMaterial, salt);
 | 
			
		||||
  console.log(keyMaterial)
 | 
			
		||||
  let key = await getKey(keyMaterial);
 | 
			
		||||
  
 | 
			
		||||
  //Remove anything form the Base64 String that isn't base64
 | 
			
		||||
  const cleanString = base64Encoded.replace(/[^A-Za-z0-9+/=]/g, '');
 | 
			
		||||
 | 
			
		||||
  // create Uint8 Array from base64 string
 | 
			
		||||
  let encryptedString = atob(cleanString)
 | 
			
		||||
  console.log(encryptedString)
 | 
			
		||||
  const encryptedContent = new Uint8Array(encryptedString.length)
 | 
			
		||||
  for (let i = 0; i < encryptedString.length; i++) {
 | 
			
		||||
      encryptedContent[i] = encryptedString.charCodeAt(i); // Populate the array with the decoded data
 | 
			
		||||
    }
 | 
			
		||||
  console.log(encryptedContent)
 | 
			
		||||
  const encryptedContentBuffer: ArrayBuffer = encryptedContent.buffer;
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    let decrypted = await window.crypto.subtle.decrypt(
 | 
			
		||||
      {
 | 
			
		||||
        name: "AES-GCM",
 | 
			
		||||
        iv: iv
 | 
			
		||||
        iv: iv      
 | 
			
		||||
      },
 | 
			
		||||
      key,
 | 
			
		||||
      encryptedContent
 | 
			
		||||
      encryptedContentBuffer
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let dec = new TextDecoder();
 | 
			
		||||
    return dec.decode(decrypted);
 | 
			
		||||
    return decrypted;
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.log(e)
 | 
			
		||||
    return `Decryption error: ${e}`
 | 
			
		||||
    errorMessage.set(`Can not decrypt file: ${e}`)
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
// Hacked together version of https://github.com/mdn/dom-examples/blob/main/web-crypto/derive-key/pbkdf2.jshttps://gist.github.com/ChaoLiangSuper/0e13f77712b68682f0d8ebabb2d63aa8
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,8 @@
 | 
			
		|||
import { writable} from 'svelte/store';
 | 
			
		||||
 | 
			
		||||
export let filename = writable("Encrypted.b64");
 | 
			
		||||
export let filename = writable("Encrypted.b64");
 | 
			
		||||
export let encryptSource = writable<File>();
 | 
			
		||||
export let encryptTarget = writable();
 | 
			
		||||
export let decryptSource = writable();
 | 
			
		||||
export let decryptDestination = writable<File>();
 | 
			
		||||
export let errorMessage = writable<string>("");
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue