-
Notifications
You must be signed in to change notification settings - Fork 54
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: aes encryption #59
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// @ts-check | ||
import * as varint from '../varint.js' | ||
import { codec } from './codec.js' | ||
|
||
const code = 0x1400 | ||
|
||
/** | ||
* @template {number} Code | ||
* @param {Object} options | ||
* @param {Uint8Array} options.bytes | ||
* @param {Uint8Array} options.iv | ||
* @param {Code} options.code | ||
* @returns {Uint8Array} | ||
*/ | ||
const encode = ({ iv, code, bytes }) => { | ||
const codeLength = varint.encodingLength(code) | ||
const ivsizeLength = varint.encodingLength(iv.byteLength) | ||
const length = codeLength + ivsizeLength + iv.byteLength + bytes.byteLength | ||
const buff = new Uint8Array(length) | ||
varint.encodeTo(code, buff) | ||
let offset = codeLength | ||
varint.encodeTo(iv.byteLength, buff, offset) | ||
offset += ivsizeLength | ||
buff.set(iv, offset) | ||
offset += iv.byteLength | ||
buff.set(bytes, offset) | ||
return buff | ||
} | ||
|
||
/** | ||
* @param {Uint8Array} bytes | ||
*/ | ||
const decode = bytes => { | ||
const [code, vlength] = varint.decode(bytes) | ||
let offset = vlength | ||
const [ivsize, ivsizeLength] = varint.decode(bytes.subarray(offset)) | ||
offset += ivsizeLength | ||
const iv = bytes.subarray(offset, offset + ivsize) | ||
offset += ivsize | ||
bytes = bytes.slice(offset) | ||
return { iv, code, bytes } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be great to give |
||
} | ||
|
||
export default codec({ encode, decode, code, name: 'encrypted' }) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import CID from '../cid.js' | ||
import random from 'js-crypto-random' | ||
import aes from 'js-crypto-aes' | ||
|
||
/** | ||
* @param {Uint8Array[]} buffers | ||
*/ | ||
const concat = buffers => Uint8Array.from(buffers.map(b => [...b]).flat()) | ||
|
||
/** | ||
* @template {'aes-gcm' | 'aes-cbc' | 'aes-ctr'} Name | ||
* @template {number} Code | ||
* @param {Object} options | ||
* @param {Name} options.name | ||
* @param {Code} options.code | ||
* @param {number} options.ivsize | ||
*/ | ||
const mkcrypto = ({ name, code, ivsize }) => { | ||
// Line below does a type cast, because type checker can't infer that | ||
// `toUpperCase` will result in desired string literal. | ||
const cyperType = /** @type {import('js-crypto-aes/dist/params').cipherTypes} */(name.toUpperCase()) | ||
/** | ||
* @param {Object} options | ||
* @param {Uint8Array} options.key | ||
* @param {Object} options.value | ||
* @param {Uint8Array} options.value.bytes | ||
* @param {Uint8Array} options.value.iv | ||
*/ | ||
const decrypt = async ({ key, value: { iv, bytes } }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It appears to me that it would make a lot of sense to give I would also expect That way decrypt turns Odd obsession with encrypt/decrypt symmetry also makes me wonder keys should be included in return types as well which would make output of encrypt input of decrypt and vice versa. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there’s more utility in having the implementation just use this destructing because then you don’t need to load the type in order to call the method. If i know that I’m going to call the “decrypt AES function” I can call this with: const iv = Buffer.from(something1)
const bytes = Buffer.from(something2)
const key = Buffer.from(something3)
decrypt({ key, value: { iv, bytes }}) But also, if I just have a block and I know that it’s encrypted I can spread it into the call. const key = Buffer.from(something)
descrypt({ key, ...block }) This makes the decrypt function’s interface work with any block based decryption function. It may or may not have an |
||
bytes = await aes.decrypt(bytes, key, { name: cyperType, iv, tagLength: 16 }) | ||
const [cid, remainder] = CID.decodeFirst(bytes) | ||
return { cid, bytes: remainder } | ||
} | ||
/** | ||
* @param {Object} options | ||
* @param {Uint8Array} options.key | ||
* @param {Uint8Array} options.bytes | ||
* @param {CID} options.cid | ||
*/ | ||
const encrypt = async ({ key, cid, bytes }) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is yet another time where I wish CID+block bytes had a proper name to go by. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually find this to be a much better pattern because there are all kinds of ways that you may arrive at a CID + Bytes (car files reads, blocks, encrypted block decodes, etc) and destructuring them into only the parts these functions need is a great practice that gives the function maximal utility. Once you “name” it you end up with a type, and with blocks that tends to mean that you also have that codec and a decoded state, which is only useful when it is and is a burden when it’s not needed. |
||
const iv = random.getRandomBytes(ivsize) | ||
const msg = concat([cid.bytes, bytes]) | ||
bytes = await aes.encrypt(msg, key, { name: cyperType, iv, tagLength: 16 }) | ||
return { bytes, iv, code } | ||
} | ||
|
||
return { | ||
code, | ||
// Note: Do a type cast becasue `toLowerCase()` turns liternal type | ||
// into a string. | ||
name: /** @type {Name} */(name.toLowerCase()), | ||
encrypt, | ||
decrypt, | ||
ivsize | ||
} | ||
} | ||
|
||
const gcm = mkcrypto({ name: 'aes-gcm', code: 0x1401, ivsize: 12 }) | ||
const cbc = mkcrypto({ name: 'aes-cbc', code: 0x1402, ivsize: 16 }) | ||
const ctr = mkcrypto({ name: 'aes-ctr', code: 0x1403, ivsize: 12 }) | ||
|
||
export { gcm, cbc, ctr } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What' the code here ? I'm bit confused because as far as I understand
0x1400
code forencrypted
codec, but then there is this othercode
which I'm not quite sure what that one represents.Some comments would be really helpful here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe it should be
cipher
instead ofcode
, that's what it's for, a generic encrypted block that uses aniv
with the cipher specified by looking up this integer in the multicodec table