Create Secret
API query: POST https://password.link/api/secrets
Required API key type: private API key
An example of encrypting a secret and sending it over to the password.link API using JavaScript. Uses jQuery, seedrandom and SJCL.
A script like this can be used on local computer or local network to conveniently create secrets on our service, however never put this to a public location without proper authentication.
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<!-- Get the latest jQuery from https://jquery.com -->
<script src="jquery-3.4.1.min.js"></script>
<!-- Get sjcl.js from https://github.com/bitwiseshiftleft/sjcl -->
<script src="sjcl.js"></script>
<!-- Get seedrandom.min.js from https://github.com/davidbau/seedrandom -->
<script src="seedrandom.min.js"></script>
<script>
// Set the base URL for the generated link
var LINK_BASE_URL = "https://some.site/secret.html";
// Set the API key here - a private API key is required
var PRIVATE_API_KEY = "private_key_abcd...";
// ----------------------------------- //
// A function for sending the secret to password.link API
function send_to_passwordlink_api(secret) {
// Create the public password part
var password_part_public = generate_string();
var password_part_public_base64 = btoa(password_part_public);
// Create the private password part
var password_part_private = generate_string();
var password_part_private_base64 = btoa(password_part_private);
// Create an SJCL compatible Base64 encoded ciphertext
var ciphertext = encrypt_secret(password_part_public, password_part_private, secret);
// The data which will be sent over to the password.link API
var data = {
"ciphertext": ciphertext,
"password_part_private": password_part_private_base64
};
// Send a request to the password.link API and process the result
$.ajax({
type: "POST",
url: "https://password.link/api/secrets",
dataType: "json",
contentType: "application/json",
headers: {
"Authorization": "ApiKey " + PRIVATE_API_KEY
},
data: JSON.stringify(data),
success: function(data) {
var secret = data.data;
var meta = data.metadata;
var secret_url = LINK_BASE_URL + "?" + secret.id + "#" + password_part_public_base64;
$("#secret").html("URL to secret: " + secret_url);
$("#secret-meta").html("Total secrets: " + meta.secrets_total);
$("#secret-meta").append("<br> Usage: " + meta.secrets_usage);
$("#secret-meta").append("<br> Allowance: " + meta.secrets_allowance);
},
error: function(data) {
var error = data.responseJSON.error;
$("#secret").html("Error: " + error.message);
}
});
}
// A function for encrypting a secret, returns a Base64 encoded SJCL compatible ciphertext
function encrypt_secret(password_part_public, password_part_private, secret) {
try {
var ciphertext_base64 = btoa(sjcl.encrypt(
password_part_private + password_part_public,
secret,
{ "mode": "gcm", "ks": 256, "iter": 10000 }
));
return ciphertext_base64;
}
// Catch and show errors
catch(e) {
$("#secret").html("Error during encryption: " + e);
}
}
// A function for generating a random 18 characters long string
function generate_string() {
var len = 18;
var chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789$|/\!_+,.-?()[]{}<>&#^*=@";
// Use seedrandom.js to create autoseeded PRNG
Math.seedrandom();
var str = "";
for (var i = 0; i < len; i++) {
str += chars.charAt(Math.floor(Math.random() * chars.length));
}
return str;
}
// Execute the send function when the button is clicked
$(document).ready(function() {
$("#secret-button").click(function(e) {
e.preventDefault();
var secret = $("#secret-input").val();
send_to_passwordlink_api(secret);
});
});
</script>
<style>
body {
font: 12px Arial;
}
.container {
max-width: 960px;
margin: 0 auto;
text-align: center;
margin-top: 60px;
}
#secret {
margin-top: 30px;
font-size: 18px;
}
#secret-meta {
margin-top: 40px;
}
.secret-form {
display: flex;
flex-flow: row wrap;
align-items: center;
flex-direction: vertical;
justify-content: center;
}
.secret-form input {
vertical-align: middle;
margin: 5px 10px 5px 0;
padding: 10px;
border: 1px solid #ddd;
width: 200px;
}
.secret-form button {
padding: 10px 20px;
border: 1px solid #ddd;
}
.secret-form button:hover {
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h2>Encrypt and create a secret on password.link</h2>
<form class="secret-form">
<input id="secret-input" type="text" name="secret">
<button id="secret-button">Encrypt and create link</button>
</form>
<!-- This div will contain the link to the secret (or error) -->
<div id="secret"></div>
<div id="secret-meta"></div>
</div>
</body>
</html>
Create Secret with Attachment
This example shows the attachment-specific parts. It assumes you have already created the secret ciphertext and password parts as shown in the Create Secret example above.
// Set these first
const PASSWORDLINK_BASE_URL = "https://password.link";
const PRIVATE_API_KEY = "private_key_abcd...";
// These values come from the Create Secret example above
const ciphertext = "...";
const passwordPartPrivate = "...";
const passwordPartPublic = "...";
const passwordPartPrivateBase64 = btoa(passwordPartPrivate);
async function createSecretWithAttachment(file) {
const createResponse = await fetch(`${PASSWORDLINK_BASE_URL}/api/secrets`, {
method: "POST",
headers: {
"Authorization": "ApiKey " + PRIVATE_API_KEY,
"Content-Type": "application/json"
},
body: JSON.stringify({
ciphertext: ciphertext,
password_part_private: passwordPartPrivateBase64,
attachment: {
file_name: file.name,
file_type: file.type || "application/octet-stream",
file_size: file.size
}
})
});
const createData = await createResponse.json();
if (!createResponse.ok) {
throw new Error(createData.error?.message || "Failed to create secret");
}
const attachmentUpload = createData.data.attachment.upload;
const fileDataUrl = await fileToDataUrl(file);
const encryptedFile = await encryptAttachment(
fileDataUrl,
passwordPartPrivate + passwordPartPublic
);
const formData = new FormData();
appendFormFields(formData, attachmentUpload.metadata);
appendFormFields(formData, attachmentUpload.fields);
formData.append("file", new Blob([encryptedFile], { type: "text/plain" }), "file.txt");
const uploadUrl = new URL(attachmentUpload.url, PASSWORDLINK_BASE_URL);
const uploadOptions = {
method: "POST",
body: formData
};
if (uploadUrl.origin === new URL(PASSWORDLINK_BASE_URL).origin) {
uploadOptions.headers = {
"Authorization": "ApiKey " + PRIVATE_API_KEY
};
} else {
uploadOptions.mode = "no-cors";
}
const uploadResponse = await fetch(uploadUrl.toString(), uploadOptions);
if (uploadResponse.status !== 0 && !uploadResponse.ok) {
throw new Error("Failed to upload attachment");
}
return createData.data.id;
}
function appendFormFields(formData, fields) {
if (!fields) {
return;
}
Object.entries(fields).forEach(([key, value]) => {
formData.append(key, value);
});
}
function fileToDataUrl(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
async function encryptAttachment(plaintext, password) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const passwordKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
"PBKDF2",
false,
["deriveKey"]
);
const key = await crypto.subtle.deriveKey(
{ name: "PBKDF2", salt: salt, iterations: 10000, hash: "SHA-256" },
passwordKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt"]
);
const cipher = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
key,
new TextEncoder().encode(plaintext)
);
return btoa(JSON.stringify({
cipher: bufferToString(cipher),
iv: bufferToString(iv),
salt: bufferToString(salt)
}));
}
function bufferToString(buffer) {
let binary = "";
const bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return binary;
}
View Secret
API query: GET https://password.link/api/secrets/<id>
Required API key type: public API key
An example of fetching, decrypting and displaying a secret using JavaScript. Uses jQuery and SJCL.
A script like this can be used to create a self-hosted view secret page.
<html>
<head>
<!-- Get the latest jQuery from https://jquery.com -->
<script src="jquery-3.4.1.min.js"></script>
<!-- Get sjcl.js from https://github.com/bitwiseshiftleft/sjcl -->
<script src="sjcl.js"></script>
<style>
body {
font: 12px Arial;
}
.container {
max-width: 960px;
margin: 0 auto;
text-align: center;
margin-top: 60px;
}
</style>
</head>
<body>
<div class="container">
<h2>Here's the secret</h2>
<!-- This div will contain the decrypted secret (or error) -->
<div id="secret"></div>
</div>
<script>
(function() {
// Set the API key here - a public API key is required
var PUBLIC_API_KEY = "public_key_abcd...";
// ----------------------------------- //
// Get the secret ID from the query string part of the URL
// E.g. https://some.site/secret.html?secret_id
var secret_id = location.search.substr(1);
// Get the public password (encryption key) part from the hash part of the URL, in Base64 format
// E.g. https://some.site/secret.html?secret_id#password_part_public
var password_part_public = location.hash.substr(1);
// Send a request to the password.link API and process the result
$.ajax({
type: "GET",
url: "https://password.link/api/secrets/" + secret_id,
dataType: "json",
headers: {
"Authorization": "ApiKey " + PUBLIC_API_KEY
},
success: function(data) {
var secret = data.data;
decrypt_secret(password_part_public, secret.password_part_private, secret.ciphertext);
},
error: function(data) {
var error = data.responseJSON.error;
$("#secret").html("Error: " + error.message);
}
});
// A function for decrypting the secret received from the password.link API
function decrypt_secret(password_part_public, password_part_private, ciphertext) {
try {
// Decrypt the secret using SJCL
// All parameters are in Base64 format
var decrypted_secret = sjcl.decrypt(atob(password_part_private) + atob(password_part_public), atob(ciphertext));
// Set the content of the element with id "secret" to the decrypted secret
// Use .textContent to avoid XSS
document.getElementById("secret").textContent = decrypted_secret;
}
// Catch and show errors
catch(e) {
document.getElementById("secret").textContent = "Error during decryption: " + e;
}
}
})();
</script>
</body>
</html>