-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat initial commit kyber pre creted users
- Loading branch information
Showing
10 changed files
with
247 additions
and
2 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
import { | ||
decryptMessageWithPrivateKey, | ||
encryptMessageWithPublicKey, | ||
} from './openpgp'; | ||
import { Kyber512 } from './kyber'; | ||
import { extendSecret, XORhex } from './utils'; | ||
|
||
const WORDS_HYBRID_MODE_IN_BASE64 = 'SHlicmlkTW9kZQ=='; // 'HybridMode' in BASE64 format | ||
|
||
/** | ||
* Encrypts message using hybrid method (ecc and kyber) if kyber key is given, else uses ecc only | ||
* | ||
* @param {Object} params - The parameters object. | ||
* @param {string} params.message - The message to encrypt. | ||
* @param {string} params.publicKeyInBase64 - The ECC public key in Base64 encoding. | ||
* @param {string} [params.publicKyberKeyBase64] - The Kyber public key in Base64 encoding. Optional. | ||
* @returns {Promise<string>} The encrypted message as a Base64-encoded string. | ||
* @throws {Error} If both ECC and Kyber keys are required but one is missing. | ||
*/ | ||
export const hybridEncryptMessageWithPublicKey = async ({ | ||
message, | ||
publicKeyInBase64, | ||
publicKyberKeyBase64, | ||
}: { | ||
message: string; | ||
publicKeyInBase64: string; | ||
publicKyberKeyBase64?: string; | ||
}): Promise<string> => { | ||
let result = ''; | ||
let plaintext = message; | ||
if (publicKyberKeyBase64) { | ||
const kem = new Kyber512(); | ||
|
||
const publicKyberKey = Buffer.from(publicKyberKeyBase64, 'base64'); | ||
const { ciphertext, sharedSecret: secret } = await kem.encapsulate( | ||
new Uint8Array(publicKyberKey), | ||
); | ||
const kyberCiphertextStr = Buffer.from(ciphertext).toString('base64'); | ||
|
||
const bits = message.length * 8; | ||
const secretHex = await extendSecret(secret, bits); | ||
const messageHex = Buffer.from(message).toString('hex'); | ||
|
||
plaintext = XORhex(messageHex, secretHex); | ||
result = WORDS_HYBRID_MODE_IN_BASE64.concat('$', kyberCiphertextStr, '$'); | ||
} | ||
|
||
const encryptedMessage = await encryptMessageWithPublicKey({ | ||
message: plaintext, | ||
publicKeyInBase64, | ||
}); | ||
const eccCiphertextStr = btoa(encryptedMessage as string); | ||
|
||
result = result.concat(eccCiphertextStr); | ||
|
||
return result; | ||
}; | ||
|
||
/** | ||
* Decrypts ciphertext using hybrid method (ecc and kyber) if kyber key is given, else uses ecc only | ||
* | ||
* @param {Object} params - The parameters object. | ||
* @param {string} params.encryptedMessageInBase64 - The encrypted message as a Base64-encoded string. | ||
* @param {string} params.privateKeyInBase64 - The ECC private key in Base64 encoding. | ||
* @param {string} [params.privateKyberKeyInBase64] - The Kyber private key in Base64 encoding. Optional. | ||
* @returns {Promise<string>} The decrypted message as a plain string. | ||
* @throws {Error} If attempting to decrypt a hybrid message without the required Kyber private key. | ||
*/ | ||
export const hybridDecryptMessageWithPrivateKey = async ({ | ||
encryptedMessageInBase64, | ||
privateKeyInBase64, | ||
privateKyberKeyInBase64, | ||
}: { | ||
encryptedMessageInBase64: string; | ||
privateKeyInBase64: string; | ||
privateKyberKeyInBase64?: string; | ||
}): Promise<string> => { | ||
let eccCiphertextStr = encryptedMessageInBase64; | ||
let kyberSecret; | ||
|
||
const ciphertexts = encryptedMessageInBase64.split('$'); | ||
const prefix = ciphertexts[0]; | ||
const isHybridMode = prefix === WORDS_HYBRID_MODE_IN_BASE64; | ||
|
||
if (isHybridMode) { | ||
if (!privateKyberKeyInBase64) { | ||
return Promise.reject( | ||
new Error('Attempted to decrypt hybrid ciphertex without Kyber key'), | ||
); | ||
} | ||
const kem = new Kyber512(); | ||
|
||
const kyberCiphertextBase64 = ciphertexts[1]; | ||
eccCiphertextStr = ciphertexts[2]; | ||
|
||
const privateKyberKey = Buffer.from(privateKyberKeyInBase64, 'base64'); | ||
const kyberCiphertext = Buffer.from(kyberCiphertextBase64, 'base64'); | ||
const decapsulateSharedSecret = await kem.decapsulate( | ||
new Uint8Array(kyberCiphertext), | ||
new Uint8Array(privateKyberKey), | ||
); | ||
kyberSecret = decapsulateSharedSecret; | ||
} | ||
|
||
const decryptedMessage = await decryptMessageWithPrivateKey({ | ||
encryptedMessage: atob(eccCiphertextStr), | ||
privateKeyInBase64, | ||
}); | ||
let result = decryptedMessage as string; | ||
if (isHybridMode) { | ||
const bits = result.length * 4; | ||
const secretHex = await extendSecret(kyberSecret, bits); | ||
const xored = XORhex(result, secretHex); | ||
result = Buffer.from(xored, 'hex').toString('utf8'); | ||
} | ||
|
||
return result; | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
export async function getKemBuilder() { | ||
const kemBuilder = await import('@dashlane/pqc-kem-kyber512-node'); | ||
return kemBuilder.default; | ||
} | ||
|
||
export class Kyber512 { | ||
private kem: Awaited<ReturnType<Awaited<ReturnType<typeof getKemBuilder>>>>; | ||
|
||
constructor() {} | ||
|
||
async init() { | ||
const kemBuilder = await getKemBuilder(); | ||
this.kem = await kemBuilder(); | ||
} | ||
|
||
/** | ||
* Generates a new Kyber-512 key pair. | ||
*/ | ||
async generateKeys(): Promise<{ | ||
publicKey: Uint8Array; | ||
privateKey: Uint8Array; | ||
}> { | ||
if (!this.kem) await this.init(); | ||
return this.kem.keypair(); | ||
} | ||
|
||
async generateKeysInBase64() { | ||
const keys = await this.generateKeys(); | ||
|
||
return { | ||
publicKey: Buffer.from(keys.publicKey).toString('base64'), | ||
privateKey: Buffer.from(keys.privateKey).toString('base64'), | ||
}; | ||
} | ||
|
||
/** | ||
* Encrypts a message using the recipient's public key. | ||
* Kyber encapsulates a shared secret along with a ciphertext. | ||
*/ | ||
async encapsulate( | ||
publicKey: Uint8Array, | ||
): Promise<{ ciphertext: Uint8Array; sharedSecret: Uint8Array }> { | ||
if (!this.kem) await this.init(); | ||
return this.kem.encapsulate(publicKey); | ||
} | ||
|
||
/** | ||
* Decrypts the ciphertext using the recipient's private key. | ||
* Returns the shared secret that matches the one from encryption. | ||
*/ | ||
async decapsulate( | ||
ciphertext: Uint8Array, | ||
privateKey: Uint8Array, | ||
): Promise<Uint8Array> { | ||
if (!this.kem) await this.init(); | ||
const { sharedSecret } = await this.kem.decapsulate(ciphertext, privateKey); | ||
return sharedSecret; | ||
} | ||
} |
File renamed without changes.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { blake3 } from 'hash-wasm'; | ||
|
||
/** | ||
* Extends the given secret to the required number of bits | ||
* @param {string} secret - The original secret | ||
* @param {number} length - The desired bitlength | ||
* @returns {Promise<string>} The extended secret of the desired bitlength | ||
*/ | ||
export function extendSecret( | ||
secret: Uint8Array, | ||
length: number, | ||
): Promise<string> { | ||
return blake3(secret, length); | ||
} | ||
|
||
/** | ||
* XORs two strings of the identical length | ||
* @param {string} a - The first string | ||
* @param {string} b - The second string | ||
* @returns {string} The result of XOR of strings a and b. | ||
*/ | ||
export function XORhex(a: string, b: string): string { | ||
let res = '', | ||
i = a.length, | ||
j = b.length; | ||
if (i != j) { | ||
throw new Error('Can XOR only strings with identical length'); | ||
} | ||
while (i-- > 0 && j-- > 0) | ||
res = | ||
(parseInt(a.charAt(i), 16) ^ parseInt(b.charAt(j), 16)).toString(16) + | ||
res; | ||
return res; | ||
} |
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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
This file contains 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