Skip to content

Commit

Permalink
Merge pull request #19 from dojyorin/dev
Browse files Browse the repository at this point in the history
crypto code improved.
  • Loading branch information
dojyorin authored Nov 25, 2022
2 parents 0745871 + c569ef3 commit 109e9ba
Show file tree
Hide file tree
Showing 10 changed files with 68 additions and 54 deletions.
17 changes: 9 additions & 8 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 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 hash = await cryptoHash(true, file); // byte array of SHA2 512 bits hash value.
const keyEcdh = await cryptoGenerateKey(true); // public/private key pair for ECDH, each in byte array.
const keyEcdsa = await cryptoGenerateKey(false); // 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 @@ -78,7 +78,7 @@ const text = " Lorem ipsum \t dolor \r sit amet.";

const encoded = ucEncode(text); // byte array in UTF-8 format.
const decoded = ucDecode(encoded); // Restored.
const hexadecimal = hexEncode(encoded); // hexadecimal string.
const hexadecimal = hexEncode(encoded); // HEX string.
const formatted = trimExtend(decoded); // formatted string.
```

Expand Down Expand Up @@ -128,7 +128,8 @@ This section is not directly related to this module, but provides a few line sni
<summary>Show more details...</summary>
<p>

**JSON Import**
**JSON Import with Type**

```ts
const {default: data} = await import("./data.json", {assert: {type: "json"}});
```
Expand All @@ -138,17 +139,17 @@ const {default: data} = await import("./data.json", {assert: {type: "json"}});
</p>

# Browser Compatible
Some methods and classes in this module don't use Deno objects internally and are browser compatible.
Some methods and classes in this module don't use `globalThis.Deno` 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
deno bundle https://deno.land/x/simple_utility@(version)/mod.compatible.ts > ./simple_utility.esm.js
```

The example uses [esbuild](https://esbuild.github.io) to minify.
This section may eventually be automated with GitHub Actions, in which case the bundled scripts will be merged into GitHub Releases, making this step unnecessary.

# API
See [Deno Document](https://deno.land/x/simple_utility/mod.ts) for details.
6 changes: 3 additions & 3 deletions deps.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export {assertEquals} from "https://deno.land/std@0.165.0/testing/asserts.ts";
export {dirname, fromFileUrl} from "https://deno.land/std@0.165.0/path/mod.ts";
export {serve} from "https://deno.land/std@0.165.0/http/mod.ts";
export {assertEquals} from "https://deno.land/std@0.166.0/testing/asserts.ts";
export {dirname, fromFileUrl} from "https://deno.land/std@0.166.0/path/mod.ts";
export {serve} from "https://deno.land/std@0.166.0/http/mod.ts";
4 changes: 2 additions & 2 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export {} from "https://deno.land/std@0.165.0/bytes/mod.ts";
export {dirname, fromFileUrl} from "https://deno.land/std@0.165.0/path/mod.ts";
export {} from "https://deno.land/std@0.166.0/bytes/mod.ts";
export {dirname, fromFileUrl} from "https://deno.land/std@0.166.0/path/mod.ts";
2 changes: 2 additions & 0 deletions src/base64.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/**
* Convert from byte array to base64 code.
* @param data byte array.
* @return base64 code.
*/
Expand All @@ -7,6 +8,7 @@ export function base64Encode(data:Uint8Array){
}

/**
* Convert from base64 code to byte array.
* @param data base64 code.
* @return byte array.
*/
Expand Down
41 changes: 21 additions & 20 deletions src/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,22 @@ export type PortableCryptoKey = Uint8Array;
*/
export type PortableCryptoKeyPair = Record<keyof CryptoKeyPair, PortableCryptoKey>;

const sizeKey = 32;
const sizeTag = 16;
const sizeIv = 12;

const nameEc = "P-384";
const nameMac = "SHA-384";

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

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

const publicKey = await crypto.subtle.importKey("spki", kp.publicKey, ec, false, []);
Expand All @@ -32,27 +39,27 @@ async function deriveSecretKey(kp:PortableCryptoKeyPair){

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

return new Uint8Array(await crypto.subtle.digest(sha, data));
}

/**
* Generate and export public/private key pair as a portable byte array.
* @param isDsa Outputs the key for ECDSA if `true`, for ECDH if `false`.
* @param isECDH Outputs the key for ECDH if `true`, for ECDSA if `false`.
* @return public/private key pair, each in byte array.
*/
export async function cryptoGenerateKey(isDsa:boolean){
const usage:KeyUsage[] = isDsa ? ["sign", "verify"] : ["deriveKey", "deriveBits"];
export async function cryptoGenerateKey(isECDH:boolean){
const usage:KeyUsage[] = isECDH ? ["deriveKey", "deriveBits"] : ["sign", "verify"];

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

const {publicKey, privateKey} = await crypto.subtle.generateKey(ec, true, usage);
Expand All @@ -71,9 +78,6 @@ export async function cryptoGenerateKey(isDsa:boolean){
* @return encrypted byte array.
*/
export async function cryptoEncrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
const sizeTag = 16;
const sizeIv = 12;

const gcm:AesGcmParams = {
name: "AES-GCM",
tagLength: sizeTag * 8,
Expand All @@ -97,9 +101,6 @@ export async function cryptoEncrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
* @return byte array.
*/
export async function cryptoDecrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
const sizeTag = 16;
const sizeIv = 12;

const gcm:AesGcmParams = {
name: "AES-GCM",
tagLength: sizeTag * 8,
Expand All @@ -120,13 +121,13 @@ export async function cryptoDecrypt(kp:PortableCryptoKeyPair, data:Uint8Array){
export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){
const ec:EcKeyAlgorithm = {
name: "ECDSA",
namedCurve: "P-384"
namedCurve: nameEc
};

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

Expand All @@ -145,13 +146,13 @@ export async function cryptoSign(k:PortableCryptoKey, data:Uint8Array){
export async function cryptoVerify(signature:Uint8Array, k:PortableCryptoKey, data:Uint8Array){
const ec:EcKeyAlgorithm = {
name: "ECDSA",
namedCurve: "P-384"
namedCurve: nameEc
};

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

Expand Down
1 change: 1 addition & 0 deletions src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export interface FetchResponseType{
}

/**
* Extended fetch function that can directly specify the response type.
* @param path Since the query string is ignored, please specify it in the `option.query` property instead of writing it directly in the URL.
* @param type The type you want to receive in the response.
* @param option `window` is removed from `RequestInit` and `query` is added to write the query string.
Expand Down
36 changes: 20 additions & 16 deletions src/minipack.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import {cryptoHash} from "./crypto.ts";
import {ucEncode, ucDecode, hexEncode} from "./text.ts";

const sizeOf = Object.freeze({
hash: 32,
name: 1,
body: 4
});
/**
* The file name and byte array pairs that make up the basic file.
*/
export type FileInit = [string, Uint8Array];

const sizeTotal = Object.values(sizeOf).reduce((a, c) => a + c, 0);
const sizeHash = 32;
const sizeName = 1;
const sizeBody = 4;
const sizeTotal = sizeHash + sizeName + sizeBody;

/**
* Encode data into a byte array in "minipack" format.
* @param files array of pair of name and byte array.
* @return byte array in "minipack" format.
* @see https://deno.land/x/simple_utility
*/
export async function minipackEncode(files:[string, Uint8Array][]){
export async function minipackEncode(files:FileInit[]){
const archive = new Uint8Array(files.reduce((a, [k, v]) => a + sizeTotal + ucEncode(k).byteLength + v.byteLength, 0));

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

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

new DataView(archive.buffer, offset).setUint8(0, name.byteLength);
offset += sizeOf.name;
offset += sizeName;

new DataView(archive.buffer, offset).setUint32(0, body.byteLength);
offset += sizeOf.body;
offset += sizeBody;

archive.set(name, offset);
offset += name.byteLength;
Expand All @@ -43,29 +46,30 @@ export async function minipackEncode(files:[string, Uint8Array][]){
}

/**
* Decode byte array in "minipack" format.
* @param archive byte array in "minipack" format.
* @return array of pair of name and byte array.
* @see https://deno.land/x/simple_utility
*/
export async function minipackDecode(archive:Uint8Array){
const files:[string, Uint8Array][] = [];
const files:FileInit[] = [];

let offset = 0;

while(offset < archive.byteLength){
const hash = archive.subarray(offset, offset += sizeOf.hash);
const hash = archive.subarray(offset, offset += sizeHash);

const ns = new DataView(archive.buffer, offset).getUint8(0);
offset += sizeOf.name;
offset += sizeName;

const bs = new DataView(archive.buffer, offset).getUint32(0);
offset += sizeOf.body;
offset += sizeBody;

const name = ucDecode(archive.subarray(offset, offset += ns));

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

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

Expand Down
2 changes: 2 additions & 0 deletions src/platform.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import {dirname, fromFileUrl} from "../deps.ts";

/**
* Check if it's running on Windows.
* @return `true` if running on Windows.
*/
export function isWin(){
return Deno.build.os === "windows";
}

/**
* Returns the system wide temporary directory path for each platform.
* @return `"C:/Windows/Temp"` if running on Windows, or `"/tmp"` if running on Linux or Mac.
*/
export function tmpPath(){
Expand Down
5 changes: 4 additions & 1 deletion src/text.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/**
* Convert from unicode string to UTF-8 byte array.
* @param data unicode string.
* @return byte array in UTF-8 format.
*/
Expand All @@ -7,6 +8,7 @@ export function ucEncode(data:string){
}

/**
* Convert from UTF-8 byte array to unicode string.
* @param data byte array in UTF-8 format.
* @return unicode string.
*/
Expand All @@ -15,8 +17,9 @@ export function ucDecode(data:Uint8Array){
}

/**
* Convert from byte array to HEX string.
* @param data byte array.
* @return hexadecimal string.
* @return HEX string.
*/
export function hexEncode(data:Uint8Array){
return [...data].map(n => n.toString(16).toUpperCase().padStart(2, "0")).join("");
Expand Down
8 changes: 4 additions & 4 deletions test/crypto.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const hashResult = new Uint8Array([
Deno.test({
name: "Crypto: Hash",
async fn(){
const hash = await cryptoHash(false, sample);
const hash = await cryptoHash(true, sample);

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

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 cryptoGenerateKey(true);
const key = await cryptoGenerateKey(false);

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

0 comments on commit 109e9ba

Please sign in to comment.