Skip to content

Commit

Permalink
Merge pull request #17 from dojyorin/dev
Browse files Browse the repository at this point in the history
improve crypto code.
  • Loading branch information
dojyorin authored Nov 24, 2022
2 parents 161cdcc + 22a4e71 commit 0745871
Show file tree
Hide file tree
Showing 6 changed files with 60 additions and 64 deletions.
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ const decoded = base64Decode(encoded); // Restored.
```ts
const file = await Deno.readFile("/path/to/binary.bin");

const hash = await deriveHash(false, file); // byte array of hash value.
const keyEcdh = await generateKeyPair(false); // public/private key pair for ECDH, each in byte array.
const keyEcdsa = await generateKeyPair(true); // public/private key pair for ECDSA, each in byte array.
const hash = await cryptoHash(false, file); // byte array of hash value.
const keyEcdh = await cryptoGenerateKey(false); // public/private key pair for ECDH, each in byte array.
const keyEcdsa = await cryptoGenerateKey(true); // public/private key pair for ECDSA, each in byte array.
const encrypted = await cryptoEncrypt(keyEcdh, file); // encrypted byte array.
const decrypted = await cryptoDecrypt(keyEcdh, encrypted); // Restored.
const signature = await cryptoSign(keyEcdsa.privateKey, data); // signature byte array.
Expand Down Expand Up @@ -137,5 +137,18 @@ const {default: data} = await import("./data.json", {assert: {type: "json"}});
</details>
</p>

# Browser Compatible
Some methods and classes in this module don't use Deno objects internally and are browser compatible.

I have prepared browser compatible code only export as [mod.compatible.ts](./mod.compatible.ts).

By bundling this, you can easily create universal utility scripts.

```sh
deno bundle https://deno.land/x/simple_utility@(version)/mod.compatible.ts | esbuild --minify | head -c -1 | tee ./simple_utility.esm.min.js
```

The example uses [esbuild](https://esbuild.github.io) to minify.

# API
See [Deno Document](https://deno.land/x/simple_utility/mod.ts) for details.
65 changes: 30 additions & 35 deletions src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,53 +6,38 @@ export type PortableCryptoKey = Uint8Array;
/**
* Each is `PortableCryptoKey` public/private key pair.
*/
export interface PortableCryptoKeyPair{
privateKey: PortableCryptoKey;
publicKey: PortableCryptoKey;
}
export type PortableCryptoKeyPair = Record<keyof CryptoKeyPair, PortableCryptoKey>;

async function parseCommonKey(kp:PortableCryptoKeyPair){
async function deriveSecretKey(kp:PortableCryptoKeyPair){
const ec:EcKeyAlgorithm = {
namedCurve: "P-384",
name: "ECDH"
};

const publicKey = await crypto.subtle.importKey("spki", kp.publicKey, ec, false, []);
const privateKey = await crypto.subtle.importKey("pkcs8", kp.privateKey, ec, false, ["deriveKey", "deriveBits"]);

const dh:EcdhKeyDeriveParams = {
name: "ECDH",
public: publicKey
namedCurve: "P-384"
};

const aes:AesDerivedKeyParams = {
name: "AES-GCM",
length: 256
};

return await crypto.subtle.deriveKey(dh, privateKey, aes, false, ["encrypt", "decrypt"]);
}

async function parseSignKey(k:PortableCryptoKey, isPrivate:boolean){
const format:KeyFormat = isPrivate ? "pkcs8" : "spki";
const usage:KeyUsage[] = isPrivate ? ["sign"] : ["verify"];
const publicKey = await crypto.subtle.importKey("spki", kp.publicKey, ec, false, []);
const privateKey = await crypto.subtle.importKey("pkcs8", kp.privateKey, ec, false, ["deriveKey", "deriveBits"]);

const ec:EcKeyAlgorithm = {
namedCurve: "P-384",
name: "ECDSA"
const dh:EcdhKeyDeriveParams = {
name: "ECDH",
public: publicKey
};

return await crypto.subtle.importKey(format, k, ec, false, usage);
return await crypto.subtle.deriveKey(dh, privateKey, aes, false, ["encrypt", "decrypt"]);
}

/**
* Derive SHA2 hash value from byte array.
* @param isHalf Use the hash length 256 bits if `true`, 512 bits if `false`.
* @param is256 Use the hash length 256 bits if `true`, 512 bits if `false`.
* @param data byte array.
* @return byte array of hash value.
*/
export async function deriveHash(isHalf:boolean, data:Uint8Array){
const sha = isHalf ? "SHA-256" : "SHA-512";
export async function cryptoHash(is256:boolean, data:Uint8Array){
const sha = is256 ? "SHA-256" : "SHA-512";

return new Uint8Array(await crypto.subtle.digest(sha, data));
}
Expand All @@ -62,12 +47,12 @@ export async function deriveHash(isHalf:boolean, data:Uint8Array){
* @param isDsa Outputs the key for ECDSA if `true`, for ECDH if `false`.
* @return public/private key pair, each in byte array.
*/
export async function generateKeyPair(isDsa:boolean){
export async function cryptoGenerateKey(isDsa:boolean){
const usage:KeyUsage[] = isDsa ? ["sign", "verify"] : ["deriveKey", "deriveBits"];

const ec:EcKeyAlgorithm = {
namedCurve: "P-384",
name: isDsa ? "ECDSA" : "ECDH"
name: isDsa ? "ECDSA" : "ECDH",
namedCurve: "P-384"
};

const {publicKey, privateKey} = await crypto.subtle.generateKey(ec, true, usage);
Expand Down Expand Up @@ -95,11 +80,11 @@ export async function cryptoEncrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
iv: crypto.getRandomValues(new Uint8Array(sizeIv))
};

const commonKey = await parseCommonKey(kp);
const secretKey = await deriveSecretKey(kp);

const output = new Uint8Array(sizeTag + sizeIv + data.byteLength);
output.set(<Uint8Array>gcm.iv, 0);
output.set(new Uint8Array(await crypto.subtle.encrypt(gcm, commonKey, data)), gcm.iv.byteLength);
output.set(new Uint8Array(await crypto.subtle.encrypt(gcm, secretKey, data)), gcm.iv.byteLength);

return output;
}
Expand All @@ -121,7 +106,7 @@ export async function cryptoDecrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
iv: data.subarray(0, sizeIv)
};

const commonKey = await parseCommonKey(kp);
const commonKey = await deriveSecretKey(kp);

return new Uint8Array(await crypto.subtle.decrypt(gcm, commonKey, data.subarray(sizeIv)));
}
Expand All @@ -133,14 +118,19 @@ export async function cryptoDecrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
* @return signature byte array.
*/
export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){
const ec:EcKeyAlgorithm = {
name: "ECDSA",
namedCurve: "P-384"
};

const dsa:EcdsaParams = {
name: "ECDSA",
hash: {
name: "SHA-384"
}
};

const privateKey = await parseSignKey(k, true);
const privateKey = await crypto.subtle.importKey("pkcs8", k, ec, false, ["sign"]);

return new Uint8Array(await crypto.subtle.sign(dsa, privateKey, data));
}
Expand All @@ -153,14 +143,19 @@ export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){
* @return `true` if correct.
*/
export async function cryptoVerify(signature:Uint8Array, k:PortableCryptoKey, data:Uint8Array){
const ec:EcKeyAlgorithm = {
name: "ECDSA",
namedCurve: "P-384"
};

const dsa:EcdsaParams = {
name: "ECDSA",
hash: {
name: "SHA-384"
}
};

const publicKey = await parseSignKey(k, false);
const publicKey = await crypto.subtle.importKey("spki", k, ec, false, ["verify"]);

return await crypto.subtle.verify(dsa, publicKey, signature, data);
}
10 changes: 5 additions & 5 deletions src/minipack.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {deriveHash} from "./crypto.ts";
import {cryptoHash} from "./crypto.ts";
import {ucEncode, ucDecode, hexEncode} from "./text.ts";

const sizeOf = <const>{
const sizeOf = Object.freeze({
hash: 32,
name: 1,
body: 4
};
});

const sizeTotal = Object.values(sizeOf).reduce((a, c) => a + c, 0);

Expand All @@ -23,7 +23,7 @@ export async function minipackEncode(files:[string, Uint8Array][]){
const name = ucEncode(k);
const body = v;

archive.set(await deriveHash(true, body), offset);
archive.set(await cryptoHash(true, body), offset);
offset += sizeOf.hash;

new DataView(archive.buffer, offset).setUint8(0, name.byteLength);
Expand Down Expand Up @@ -65,7 +65,7 @@ export async function minipackDecode(archive:Uint8Array){

const body = archive.subarray(offset, offset += bs);

if(hexEncode(hash) !== hexEncode(await deriveHash(true, body))){
if(hexEncode(hash) !== hexEncode(await cryptoHash(true, body))){
throw new Error();
}

Expand Down
18 changes: 3 additions & 15 deletions src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@ import {dirname, fromFileUrl} from "../deps.ts";
* @return `true` if running on Windows.
*/
export function isWin(){
const os = Deno?.build.os;

if(!os){
throw new Error();
}

return os === "windows";
return Deno.build.os === "windows";
}

/**
* @return `"C:/Windows/Temp"` if running on Windows, or `"/tmp"` if running on Linux or Mac.
*/
export function tmpPath(){
switch(Deno?.build.os){
switch(Deno.build.os){
case "windows": return "C:/Windows/Temp";
case "linux": return "/tmp";
case "darwin": return "/tmp";
Expand All @@ -29,11 +23,5 @@ export function tmpPath(){
* Move current directory to `Deno.mainModule`.
*/
export function cwdMain(){
const ep = Deno?.mainModule;

if(!ep){
throw new Error();
}

Deno?.chdir(fromFileUrl(dirname(ep)));
Deno.chdir(fromFileUrl(dirname(Deno.mainModule)));
}
2 changes: 1 addition & 1 deletion src/web.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Possible types of JSON.
*/
export type JsonStruct = string | number | boolean | null | JsonStruct[] | {[key: string]: JsonStruct;};
export type JsonStruct = string | number | boolean | null | JsonStruct[] | {[key in string]: JsonStruct};

/**
* Possible input types for `URLSearchParams`.
Expand Down
10 changes: 5 additions & 5 deletions test/crypto.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {assertEquals} from "../deps.test.ts";
import {deriveHash, generateKeyPair, cryptoEncrypt, cryptoDecrypt, cryptoSign, cryptoVerify} from "../src/crypto.ts";
import {cryptoHash, cryptoGenerateKey, cryptoEncrypt, cryptoDecrypt, cryptoSign, cryptoVerify} from "../src/crypto.ts";

const sample = new Uint8Array([0x02, 0xF2, 0x5D, 0x1F, 0x1C, 0x34, 0xB9, 0x2F]);

Expand All @@ -17,7 +17,7 @@ const hashResult = new Uint8Array([
Deno.test({
name: "Crypto: Hash",
async fn(){
const hash = await deriveHash(false, sample);
const hash = await cryptoHash(false, sample);

assertEquals(hash, hashResult);
}
Expand All @@ -26,8 +26,8 @@ Deno.test({
Deno.test({
name: "Crypto: Encrypt and Decrypt",
async fn(){
const key1 = await generateKeyPair(false);
const key2 = await generateKeyPair(false);
const key1 = await cryptoGenerateKey(false);
const key2 = await cryptoGenerateKey(false);

const encrypt = await cryptoEncrypt({
publicKey: key1.publicKey,
Expand All @@ -46,7 +46,7 @@ Deno.test({
Deno.test({
name: "Crypto: Sign and Verify",
async fn(){
const key = await generateKeyPair(true);
const key = await cryptoGenerateKey(true);

const signature = await cryptoSign(key.privateKey, sample);
const verify = await cryptoVerify(signature, key.publicKey, sample);
Expand Down

0 comments on commit 0745871

Please sign in to comment.