E2E 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.

Recent Requests
Log in to see full request history
TimeStatusUser Agent
Retrieving recent requests…
LoadingLoading…

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);
})
Body Params
string
required
Defaults to <BASE64 ENCODED, ENCRYPTED SECRET>

Ecrypted secret using AES-GCM with a symmetric key derived from a cryptographically random 64 character passphrase using PBKDF2. 1,000,000 salt rounds required. Then base64 encode the encrypted secret.

string
required
Defaults to <SHA256 HASH>

SHA256 hash of the password. This is NOT the hash of the derived encryption key.

int32
Defaults to 1

Number of views before the link expires. Valid ranges: 1 to 50. -1 for unlimited.

int32
Defaults to 1

Number of days before the link expires. Valid range: 1 to 90.

string
required
Defaults to pbkdf2

The key derivation function used. Must by "pbkdf2".

int32
required
Defaults to 1000000

Number of salt rounds used by KDF. Must be "1000000".

Response

Language
Credentials
Basic
base64
:
LoadingLoading…
Response
Click Try It! to start a request and see the response here! Or choose an example:
application/json