post https://api.doppler.com/v1/share/secrets/encrypted
Generate a Doppler Share link by sending an encrypted secret. The receive flow the user goes through will be end-to-end encrypted where the encrypted secret will be decrypted on the browser.
Encryption Examples
// Imports
const crypto = require("crypto");
const pbkdf2Async = util.promisify(crypto.pbkdf2);
// Constants
const algorithm = "aes-256-gcm";
const keyLength = 256 / 8;
const ivLength = 12;
const saltLength = 16;
const saltRounds = 100000;
const authTagLength = 16;
// Encryption
async function encrypt(plainText) {
// Generate Password
const password = crypto.randomBytes(32).toString("hex");
const hashedPassword = crypto.createHash("sha256").update(password).digest("hex");
// Derive key with PBKD2
const salt = crypto.randomBytes(saltLength);
const iv = crypto.randomBytes(ivLength);
const key = await pbkdf2Async(password, salt, saltRounds, keyLength, "sha256");
// Encrypt plain text
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encryptedData = Buffer.concat([cipher.update(plainText, "utf8"), cipher.final()]);
const authTag = cipher.getAuthTag();
return {
encrypted_secret: Buffer.concat([salt, iv, authTag, encryptedData]).toString("base64"),
password: password,
hashedPassword: hashedPassword
};
}
// Testing
encrypt("SECRET TO ENCRYPT").then(payload => {
console.log(payload);
})
class CryptoLib {
constructor() {
this._encoder = new TextEncoder();
this._decoder = new TextDecoder();
this._saltRounds = 100000;
this._keyLength = 256;
this._ivLength = 12;
this._saltLength = 16;
this._authTagLength = 16;
}
randomString(length) {
const validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let array = this.randomArray(length);
array = array.map((x) => validChars.charCodeAt(x % validChars.length));
return String.fromCharCode(...array);
}
randomArray(length) {
return window.crypto.getRandomValues(new Uint8Array(length));
}
// Source: https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#converting_a_digest_to_a_hex_string
async sha256(text) {
const msgUint8 = this._encoder.encode(text); // encode as (utf-8) Uint8Array
const hashBuffer = await crypto.subtle.digest("SHA-256", msgUint8); // hash the message
const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join(""); // convert bytes to hex string
return hashHex;
}
async generatePassword() {
const password = this.randomString(64);
return {
password: password,
hashedPassword: await this.sha256(password),
};
}
async encrypt(secret) {
const { password, hashedPassword } = await this.generatePassword();
const salt = this.randomArray(this._saltLength);
const iv = this.randomArray(this._ivLength);
const aesKey = await this._deriveKey(password, salt, this._saltRounds, ["encrypt"]);
const encryptedContent = await window.crypto.subtle.encrypt(
{
name: "AES-GCM",
iv: iv,
tagLength: this._authTagLength * 8,
},
aesKey,
this._encoder.encode(secret)
);
const authTag = new Uint8Array(encryptedContent.slice(encryptedContent.byteLength - this._authTagLength));
const encryptedContentArray = new Uint8Array(encryptedContent.slice(0, encryptedContent.byteLength - this._authTagLength));
const buffer = new Uint8Array(salt.byteLength + iv.byteLength + authTag.byteLength + encryptedContentArray.byteLength);
buffer.set(salt, 0);
buffer.set(iv, salt.byteLength);
buffer.set(authTag, salt.byteLength + iv.byteLength);
buffer.set(encryptedContentArray, salt.byteLength + iv.byteLength + authTag.byteLength);
return {
encryptedSecret: this._bufferToBase64(buffer),
password: password,
hashedPassword: hashedPassword,
kdf: "pbkdf2",
saltRounds: this._saltRounds,
};
}
async _deriveKey(password, salt, saltRounds, keyUsage) {
const passwordKey = await window.crypto.subtle.importKey("raw", this._encoder.encode(password), "PBKDF2", false, [
"deriveKey",
]);
return window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: salt,
iterations: saltRounds,
hash: "SHA-256",
},
passwordKey,
{ name: "AES-GCM", length: this._keyLength },
false,
keyUsage
);
}
_bufferToBase64(buffer) {
return btoa(String.fromCharCode.apply(null, buffer));
}
}
// Testing
const cryptoLib = new CryptoLib();
cryptoLib.encrypt("SECRET TO ENCRYPT").then(payload => {
console.log(payload);
})