Skip to content

Commit be57755

Browse files
authored
Merge pull request #6 from phasehq/feat--zero-keys
feat: zero keys
2 parents 054c5cb + 37c92b5 commit be57755

File tree

6 files changed

+51
-10
lines changed

6 files changed

+51
-10
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,17 @@ const ciphertext = await phase.encrypt("hello world");
3333
```js
3434
const plaintext = await phase.decrypt(ciphertext);
3535
```
36+
37+
## Development
38+
39+
### Install dependencies
40+
41+
`npm install`
42+
43+
### Build
44+
45+
`npm run build`
46+
47+
### Run tests
48+
49+
`npm test`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@phase.dev/phase-node",
3-
"version": "0.3.0",
3+
"version": "1.0.0",
44
"description": "Node.js Server SDK for Phase",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ export default class Phase {
7474
symmetricKeys.sharedTx
7575
);
7676

77+
// Use sodium.memzero to wipe the keys from memory
78+
sodium.memzero(oneTimeKeyPair.privateKey);
79+
sodium.memzero(symmetricKeys.sharedTx);
80+
sodium.memzero(symmetricKeys.sharedRx);
81+
7782
resolve(
7883
`ph:${PH_VERSION}:${sodium.to_hex(
7984
oneTimeKeyPair.publicKey
@@ -117,7 +122,7 @@ export default class Phase {
117122
const sessionKeys = await serverSessionKeys(
118123
{
119124
publicKey: sodium.from_hex(this.appPubKey) as Uint8Array,
120-
privateKey: sodium.from_hex(appPrivKey) as Uint8Array,
125+
privateKey: appPrivKey,
121126
},
122127
sodium.from_hex(ciphertext.pubKey)
123128
);
@@ -127,6 +132,11 @@ export default class Phase {
127132
sessionKeys.sharedRx
128133
);
129134

135+
// Use sodium.memzero to wipe the keys from memory
136+
sodium.memzero(sessionKeys.sharedRx);
137+
sodium.memzero(sessionKeys.sharedTx);
138+
sodium.memzero(appPrivKey);
139+
130140
resolve(plaintext);
131141
} catch (error) {
132142
reject(`Something went wrong: ${error}`);

src/utils/crypto.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,14 +159,16 @@ const xorUint8Arrays = (a: Uint8Array, b: Uint8Array): Uint8Array => {
159159
* Reconstructs a secret given an array of shares
160160
*
161161
* @param {string[]} shares Array of shares encoded as hex string
162-
* @returns {string} The reconstructed secret as a hex-encoded string
162+
* @returns {Uint8Array} The reconstructed secret
163163
*/
164-
export const reconstructSecret = async (shares: string[]): Promise<string> => {
164+
export const reconstructSecret = async (
165+
shares: string[]
166+
): Promise<Uint8Array> => {
165167
await _sodium.ready;
166168
const sodium = _sodium;
167169
const byteShares = shares.map((share) => sodium.from_hex(share));
168170

169171
const secret = byteShares.reduce((prev, curr) => xorUint8Arrays(prev, curr));
170172

171-
return sodium.to_hex(secret);
173+
return secret;
172174
};

tests/index.test.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ describe("Phase", () => {
3939
});
4040

4141
describe("Encryption", () => {
42+
const APP_ID =
43+
"phApp:v1:cd2d579490fd794f1640590220de86a3676fa7979d419056bc631741b320b701";
44+
const APP_SECRET =
45+
"pss:v1:a7a0822aa4a4e4d37919009264200ba6ab978d92c8b4f7db5ae9ce0dfaf604fe:801605dfb89822ff52957abe39949bcfc44b9058ad81de58dd54fb0b110037b4b2bbde5a1143d31bbb3895f72e4ee52f5bd:625d395987f52c37022063eaf9b6260cad9ca03c99609213f899cae7f1bb04e7";
46+
4247
test("Check if Phase encrypt returns a valid ph", async () => {
43-
const APP_ID =
44-
"phApp:v1:cd2d579490fd794f1640590220de86a3676fa7979d419056bc631741b320b701";
45-
const APP_SECRET =
46-
"pss:v1:a7a0822aa4a4e4d37919009264200ba6ab978d92c8b4f7db5ae9ce0dfaf604fe:801605dfb89822ff52957abe39949bcfc44b9058ad81de58dd54fb0b110037b4b2bbde5a1143d31bbb3895f72e4ee52f5bd:625d395987f52c37022063eaf9b6260cad9ca03c99609213f899cae7f1bb04e7";
4748
const phase = new Phase(APP_ID, APP_SECRET);
4849
const plaintext = "Signal";
4950
const tag = "Phase Tag";
@@ -59,6 +60,20 @@ describe("Phase", () => {
5960
expect(segments[2]).toMatch(/^[0-9a-f]+$/);
6061
expect(segments[3]).toMatch(/^[0-9a-f]+$/);
6162
});
63+
64+
test("Check if Phase encrypt always produces ciphertexts (ph:*) of the same length for the same plaintext", async () => {
65+
const phase = new Phase(APP_ID, APP_SECRET);
66+
const data = "hello world";
67+
const numOfTrials = 10;
68+
const ciphertextLengths = new Set<number>();
69+
70+
for (let i = 0; i < numOfTrials; i++) {
71+
const ciphertext = await phase.encrypt(data);
72+
ciphertextLengths.add((ciphertext as string).length);
73+
}
74+
75+
expect(ciphertextLengths.size).toBe(1);
76+
});
6277
});
6378

6479
describe("Decryption", () => {

version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export const LIB_VERSION = "0.3.0";
1+
export const LIB_VERSION = "1.0.0";

0 commit comments

Comments
 (0)