- 扫描 ID:
- bad07152-126f-4d2a-9aea-50810c50dc86已完成
- 提交的 URL:
- https://grpegg6643.prensacheck.com//@已重定向
- 报告完成时间:
链接 · 找到 0 个
从页面中识别出的传出链接
JavaScript 变量 · 找到 3 个
在页面窗口对象上加载的全局 JavaScript 变量是在函数外部声明的变量,可以从当前范围内的代码中的任何位置访问
名称 | 类型 |
---|---|
onbeforetoggle | object |
documentPictureInPicture | object |
onscrollend | object |
控制台日志消息 · 找到 2 条
记录到 Web 控制台的消息
类型 | 类别 | 记录 |
---|---|---|
warning | other |
|
verbose | dom |
|
HTML
页面的原始 HTML 正文
<!DOCTYPE html><html class="staticrypt-html"><head>
<meta charset="utf-8">
<title>PDF Document</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- do not cache this page -->
<meta http-equiv="cache-control" content="max-age=0">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT">
<meta http-equiv="pragma" content="no-cache">
<style>
.staticrypt-hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
}
.staticrypt-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
box-sizing: border-box;
}
.staticrypt-form {
position: relative;
z-index: 1;
background: #ffffff;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.staticrypt-form input[type="password"],
input[type="text"] {
background: inherit;
border: 0;
box-sizing: border-box; /* This ensures padding is included in the total width */
font-size: 14px;
outline: 0;
padding: 15px 30px 15px 15px; /* Adjust the padding to ensure there is space for the icon */
width: 100%;
}
.staticrypt-password-container {
position: relative;
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
box-sizing: border-box;
}
.staticrypt-toggle-password-visibility {
cursor: pointer;
height: 20px;
opacity: 60%;
padding: 13px;
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 20px;
}
.staticrypt-form .staticrypt-decrypt-button {
text-transform: uppercase;
outline: 0;
background: #061bd1;
width: 100%;
border: 0;
padding: 15px;
color: #ffffff;
font-size: 14px;
cursor: pointer;
}
.staticrypt-form .staticrypt-decrypt-button:hover,
.staticrypt-form .staticrypt-decrypt-button:active,
.staticrypt-form .staticrypt-decrypt-button:focus {
background: #061bd1;
filter: brightness(92%);
}
.staticrypt-html {
height: 100%;
}
.staticrypt-body {
height: 100%;
margin: 0;
}
.staticrypt-content {
height: 100%;
margin-bottom: 1em;
background: #e6e6e6;
font-family: "Arial", sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.staticrypt-instructions {
margin-top: -1em;
margin-bottom: 1em;
}
.staticrypt-title {
font-size: 1.5em;
}
label.staticrypt-remember {
display: flex;
align-items: center;
margin-bottom: 1em;
}
.staticrypt-remember input[type="checkbox"] {
transform: scale(1.5);
margin-right: 1em;
}
.hidden {
display: none !important;
}
.staticrypt-spinner-container {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.staticrypt-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: 0.25em solid gray;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border 0.75s linear infinite;
animation: spinner-border 0.75s linear infinite;
animation-duration: 0.75s;
animation-timing-function: linear;
animation-delay: 0s;
animation-iteration-count: infinite;
animation-direction: normal;
animation-fill-mode: none;
animation-play-state: running;
animation-name: spinner-border;
}
@keyframes spinner-border {
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body class="staticrypt-body">
<div id="staticrypt_loading" class="staticrypt-spinner-container hidden">
<div class="staticrypt-spinner"></div>
</div>
<div id="staticrypt_content" class="staticrypt-content">
<div class="staticrypt-page">
<div class="staticrypt-form">
<div class="staticrypt-instructions">
<p><img alt="icon" class="Do8Zj" crossorigin="use-credentials" data-custom="AAMkADMzN2YxMWEwLTgxMGYtNGZiYS04OTI2LTFmMDE5M2E5MmU1YwBGAAAAAAAx6g5XHttjSrELiRJdI7p4BwDR2pHtnBQSRrz63dfsQwUMAAAAAIp8AADR2pHtnBQSRrz63dfsQwUMAAGBRc3GAAABEgAQAMswd1VmNYFFmaLAooD3xbM%3D" data-imagetype="AttachmentByCid" data-outlook-trace="F:0|T:1" fetchpriority="high" height="auto" naturalheight="0" naturalwidth="0" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAABDFJREFUeF7tnU9IFFEcx9+oLWF0ESLomgXWzZSunYugSwvF+qfLEmRidOuSXbwZElqwIa1/sNqggtBzncIywaCMskMIgaVmRBKr28YrZxvXXf3N+zNvnvOdi67+/sx8P+/93rz3dmcdhsOoAo7R7EjOAMBwIwAAADCsgOH06AG2A0gmk9XZX8sdecbqDF8LKb3jOPOVK7me/pGRTyQHzUbSPaC1OXHFFvFdLSvybLki97srDBCkAbQ0J4ZisVgsmTx/qqGxMaa5wQiHb21pGubOtbUH9s/MfPgYFgjKAKRu98eF1QnA0QXQfb0ncetm3/OwQFACgOuXHhhKBKCjcAoXAD/PhYUFFhYIkQTAKYYFQmQBhAVCpAGEAULkAZiGAABrw7qpMQEAPPdVJiAAQNGNbdAQAKDEzCJICABQZmoXFAQA2GRuHQQEANhicUM3BAAgrC7phAAABAA6J2sAQASgC0JkAHS0Xxxd+r70ra2tPS6zceQtR3x7Mz0wdMkHww2mkQGQuX9vdmxs9JmMWKV8BwaHm2RiRgZANptljx89nJ14Mf7uy/z8nIxoXl8AUKWkzzjuDhsA+BROlTkAqFJSMA4ACAqnyg0AVCkpGAcABIVT5QYAqpQUjGMNgImX4yy3uip4mWbdKquqWEPj0ZInAQABsNkWAALQyUgKa3qAEXUCSAoAAYi8WQoAAIB/CvAPaPCf5d6ejrugzVuK9uVoADAMwHCl0JYeY4A2aWmBAYCmkzYrANAmLS2wNQAwCBsehAHAMABah7bPypoSZJ+0tDMGAJpO2qwAQJu0tMDWAMAgbHgQBgDDAGgd2j4ra0qQfdLSzhgAaDppswIAbdLSAgMATSdtVgCgTVpa4G0JYHFshC2O3mUrXz/TVPBptWPPPlZz4gyrOX7Wp+dG820HgIs/l+6WFoYSYG/rZWkIxgG8idd3Onl2lXLBpWwqd+1mB+88Lfxr5sJJbS2/OD/vCbV9Twp/fn/uGMv9/CF6KSzvsGuHM5OdIgGE3xXx9nR9XiSh16cu86rwcjp+pPA7B8MBqTy4wFxo9yiXWzTnoQeTQloKOfGT1AnAK46oIKX8vJABgDFWTgQAIDQ79ID1IqEEERoNSlCRSChBTPw7ZFCCUIIIRWe9CUoQStCGRoN5AGPM2zN8d6s1B9wFEZRDCUIJQgnCWpCnDWAeENJ5AFZDCQMa9gP+i2RkP8BNv9XHVAks/5pgR4yqVJGdKgAuBOwJ+wShEoDP1EbNje8Jqy5BRtUUSA4AAqKpdAEAlWoKxAIAAdFUuoQOgOxTyVWKozMWfwb166mpbG/vjQzPY/zRxTZ+obMqQA5j0+nB4S6ZeML7AW5S277SXEYsry8XP7azuieVSi3LxJQGIJMcvhKLcRBPjQLoAWp0FI4CAMLSqXEEADU6CkcBAGHp1Dj+AeTxh514ccUrAAAAAElFTkSuQmCC" style="border: 0px; font: inherit; margin: 0px; padding: 0px; vertical-align: baseline; color: inherit; animation: 0.1s ease-out 0s 1 normal none running Do8Zj; display: block; width: 40px; min-height: auto; min-width: auto;" width="32"></p>
<p class="staticrypt-title">PDF Document</p>
<p></p><p>Type in <strong>rfp </strong>to access on Microsoft OneDrive.</p>
<p></p>
</div>
<hr class="staticrypt-hr">
<form id="staticrypt-form" action="#" method="post">
<div class="staticrypt-password-container">
<input id="staticrypt-password" type="password" name="password" placeholder="Enter 'rfp'" autofocus="">
<img class="staticrypt-toggle-password-visibility" alt="template_toggle_show" title="template_toggle_show" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNS4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTM4LjggNS4xQzI4LjQtMy4xIDEzLjMtMS4yIDUuMSA5LjJTLTEuMiAzNC43IDkuMiA0Mi45bDU5MiA0NjRjMTAuNCA4LjIgMjUuNSA2LjMgMzMuNy00LjFzNi4zLTI1LjUtNC4xLTMzLjdMNTI1LjYgMzg2LjdjMzkuNi00MC42IDY2LjQtODYuMSA3OS45LTExOC40YzMuMy03LjkgMy4zLTE2LjcgMC0yNC42Yy0xNC45LTM1LjctNDYuMi04Ny43LTkzLTEzMS4xQzQ2NS41IDY4LjggNDAwLjggMzIgMzIwIDMyYy02OC4yIDAtMTI1IDI2LjMtMTY5LjMgNjAuOEwzOC44IDUuMXpNMjIzLjEgMTQ5LjVDMjQ4LjYgMTI2LjIgMjgyLjcgMTEyIDMyMCAxMTJjNzkuNSAwIDE0NCA2NC41IDE0NCAxNDRjMCAyNC45LTYuMyA0OC4zLTE3LjQgNjguN0w0MDggMjk0LjVjOC40LTE5LjMgMTAuNi00MS40IDQuOC02My4zYy0xMS4xLTQxLjUtNDcuOC02OS40LTg4LjYtNzEuMWMtNS44LS4yLTkuMiA2LjEtNy40IDExLjdjMi4xIDYuNCAzLjMgMTMuMiAzLjMgMjAuM2MwIDEwLjItMi40IDE5LjgtNi42IDI4LjNsLTkwLjMtNzAuOHpNMzczIDM4OS45Yy0xNi40IDYuNS0zNC4zIDEwLjEtNTMgMTAuMWMtNzkuNSAwLTE0NC02NC41LTE0NC0xNDRjMC02LjkgLjUtMTMuNiAxLjQtMjAuMkw4My4xIDE2MS41QzYwLjMgMTkxLjIgNDQgMjIwLjggMzQuNSAyNDMuN2MtMy4zIDcuOS0zLjMgMTYuNyAwIDI0LjZjMTQuOSAzNS43IDQ2LjIgODcuNyA5MyAxMzEuMUMxNzQuNSA0NDMuMiAyMzkuMiA0ODAgMzIwIDQ4MGM0Ny44IDAgODkuOS0xMi45IDEyNi4yLTMyLjVMMzczIDM4OS45eiIvPjwvc3ZnPg==">
</div>
<label id="staticrypt-remember-label" class="staticrypt-remember hidden">
<input id="staticrypt-remember" type="checkbox" name="remember">
Remember me
</label>
<input type="submit" class="staticrypt-decrypt-button" value="View PDF">
</form>
</div>
</div>
</div>
<script>
// these variables will be filled when generating the file - the template format is 'variable_name'
const staticryptInitiator =
((function(){
const exports = {};
const cryptoEngine = ((function(){
const exports = {};
const { subtle } = crypto;
const IV_BITS = 16 * 8;
const HEX_BITS = 4;
const ENCRYPTION_ALGO = "AES-CBC";
/**
* Translates between utf8 encoded hexadecimal strings
* and Uint8Array bytes.
*/
const HexEncoder = {
/**
* hex string -> bytes
* @param {string} hexString
* @returns {Uint8Array}
*/
parse: function (hexString) {
if (hexString.length % 2 !== 0) throw "Invalid hexString";
const arrayBuffer = new Uint8Array(hexString.length / 2);
for (let i = 0; i < hexString.length; i += 2) {
const byteValue = parseInt(hexString.substring(i, i + 2), 16);
if (isNaN(byteValue)) {
throw "Invalid hexString";
}
arrayBuffer[i / 2] = byteValue;
}
return arrayBuffer;
},
/**
* bytes -> hex string
* @param {Uint8Array} bytes
* @returns {string}
*/
stringify: function (bytes) {
const hexBytes = [];
for (let i = 0; i < bytes.length; ++i) {
let byteString = bytes[i].toString(16);
if (byteString.length < 2) {
byteString = "0" + byteString;
}
hexBytes.push(byteString);
}
return hexBytes.join("");
},
};
/**
* Translates between utf8 strings and Uint8Array bytes.
*/
const UTF8Encoder = {
parse: function (str) {
return new TextEncoder().encode(str);
},
stringify: function (bytes) {
return new TextDecoder().decode(bytes);
},
};
/**
* Salt and encrypt a msg with a password.
*/
async function encrypt(msg, hashedPassword) {
// Must be 16 bytes, unpredictable, and preferably cryptographically random. However, it need not be secret.
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#parameters
const iv = crypto.getRandomValues(new Uint8Array(IV_BITS / 8));
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["encrypt"]);
const encrypted = await subtle.encrypt(
{
name: ENCRYPTION_ALGO,
iv: iv,
},
key,
UTF8Encoder.parse(msg)
);
// iv will be 32 hex characters, we prepend it to the ciphertext for use in decryption
return HexEncoder.stringify(iv) + HexEncoder.stringify(new Uint8Array(encrypted));
}
exports.encrypt = encrypt;
/**
* Decrypt a salted msg using a password.
*
* @param {string} encryptedMsg
* @param {string} hashedPassword
* @returns {Promise<string>}
*/
async function decrypt(encryptedMsg, hashedPassword) {
const ivLength = IV_BITS / HEX_BITS;
const iv = HexEncoder.parse(encryptedMsg.substring(0, ivLength));
const encrypted = encryptedMsg.substring(ivLength);
const key = await subtle.importKey("raw", HexEncoder.parse(hashedPassword), ENCRYPTION_ALGO, false, ["decrypt"]);
const outBuffer = await subtle.decrypt(
{
name: ENCRYPTION_ALGO,
iv: iv,
},
key,
HexEncoder.parse(encrypted)
);
return UTF8Encoder.stringify(new Uint8Array(outBuffer));
}
exports.decrypt = decrypt;
/**
* Salt and hash the password so it can be stored in localStorage without opening a password reuse vulnerability.
*
* @param {string} password
* @param {string} salt
* @returns {Promise<string>}
*/
async function hashPassword(password, salt) {
// we hash the password in multiple steps, each adding more iterations. This is because we used to allow less
// iterations, so for backward compatibility reasons, we need to support going from that to more iterations.
let hashedPassword = await hashLegacyRound(password, salt);
hashedPassword = await hashSecondRound(hashedPassword, salt);
return hashThirdRound(hashedPassword, salt);
}
exports.hashPassword = hashPassword;
/**
* This hashes the password with 1k iterations. This is a low number, we need this function to support backwards
* compatibility.
*
* @param {string} password
* @param {string} salt
* @returns {Promise<string>}
*/
function hashLegacyRound(password, salt) {
return pbkdf2(password, salt, 1000, "SHA-1");
}
exports.hashLegacyRound = hashLegacyRound;
/**
* Add a second round of iterations. This is because we used to use 1k, so for backwards compatibility with
* remember-me/autodecrypt links, we need to support going from that to more iterations.
*
* @param hashedPassword
* @param salt
* @returns {Promise<string>}
*/
function hashSecondRound(hashedPassword, salt) {
return pbkdf2(hashedPassword, salt, 14000, "SHA-256");
}
exports.hashSecondRound = hashSecondRound;
/**
* Add a third round of iterations to bring total number to 600k. This is because we used to use 1k, then 15k, so for
* backwards compatibility with remember-me/autodecrypt links, we need to support going from that to more iterations.
*
* @param hashedPassword
* @param salt
* @returns {Promise<string>}
*/
function hashThirdRound(hashedPassword, salt) {
return pbkdf2(hashedPassword, salt, 585000, "SHA-256");
}
exports.hashThirdRound = hashThirdRound;
/**
* Salt and hash the password so it can be stored in localStorage without opening a password reuse vulnerability.
*
* @param {string} password
* @param {string} salt
* @param {int} iterations
* @param {string} hashAlgorithm
* @returns {Promise<string>}
*/
async function pbkdf2(password, salt, iterations, hashAlgorithm) {
const key = await subtle.importKey("raw", UTF8Encoder.parse(password), "PBKDF2", false, ["deriveBits"]);
const keyBytes = await subtle.deriveBits(
{
name: "PBKDF2",
hash: hashAlgorithm,
iterations,
salt: UTF8Encoder.parse(salt),
},
key,
256
);
return HexEncoder.stringify(new Uint8Array(keyBytes));
}
function generateRandomSalt() {
const bytes = crypto.getRandomValues(new Uint8Array(128 / 8));
return HexEncoder.stringify(new Uint8Array(bytes));
}
exports.generateRandomSalt = generateRandomSalt;
async function signMessage(hashedPassword, message) {
const key = await subtle.importKey(
"raw",
HexEncoder.parse(hashedPassword),
{
name: "HMAC",
hash: "SHA-256",
},
false,
["sign"]
);
const signature = await subtle.sign("HMAC", key, UTF8Encoder.parse(message));
return HexEncoder.stringify(new Uint8Array(signature));
}
exports.signMessage = signMessage;
function getRandomAlphanum() {
const possibleCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let byteArray;
let parsedInt;
// Keep generating new random bytes until we get a value that falls
// within a range that can be evenly divided by possibleCharacters.length
do {
byteArray = crypto.getRandomValues(new Uint8Array(1));
// extract the lowest byte to get an int from 0 to 255 (probably unnecessary, since we're only generating 1 byte)
parsedInt = byteArray[0] & 0xff;
} while (parsedInt >= 256 - (256 % possibleCharacters.length));
// Take the modulo of the parsed integer to get a random number between 0 and totalLength - 1
const randomIndex = parsedInt % possibleCharacters.length;
return possibleCharacters[randomIndex];
}
/**
* Generate a random string of a given length.
*
* @param {int} length
* @returns {string}
*/
function generateRandomString(length) {
let randomString = "";
for (let i = 0; i < length; i++) {
randomString += getRandomAlphanum();
}
return randomString;
}
exports.generateRandomString = generateRandomString;
return exports;
})());
const codec = ((function(){
const exports = {};
/**
* Initialize the codec with the provided cryptoEngine - this return functions to encode and decode messages.
*
* @param cryptoEngine - the engine to use for encryption / decryption
*/
function init(cryptoEngine) {
const exports = {};
/**
* Top-level function for encoding a message.
* Includes password hashing, encryption, and signing.
*
* @param {string} msg
* @param {string} password
* @param {string} salt
*
* @returns {string} The encoded text
*/
async function encode(msg, password, salt) {
const hashedPassword = await cryptoEngine.hashPassword(password, salt);
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encode = encode;
/**
* Encode using a password that has already been hashed. This is useful to encode multiple messages in a row, that way
* we don't need to hash the password multiple times.
*
* @param {string} msg
* @param {string} hashedPassword
*
* @returns {string} The encoded text
*/
async function encodeWithHashedPassword(msg, hashedPassword) {
const encrypted = await cryptoEngine.encrypt(msg, hashedPassword);
// we use the hashed password in the HMAC because this is effectively what will be used a password (so we can store
// it in localStorage safely, we don't use the clear text password)
const hmac = await cryptoEngine.signMessage(hashedPassword, encrypted);
return hmac + encrypted;
}
exports.encodeWithHashedPassword = encodeWithHashedPassword;
/**
* Top-level function for decoding a message.
* Includes signature check and decryption.
*
* @param {string} signedMsg
* @param {string} hashedPassword
* @param {string} salt
* @param {int} backwardCompatibleAttempt
* @param {string} originalPassword
*
* @returns {Object} {success: true, decoded: string} | {success: false, message: string}
*/
async function decode(signedMsg, hashedPassword, salt, backwardCompatibleAttempt = 0, originalPassword = "") {
const encryptedHMAC = signedMsg.substring(0, 64);
const encryptedMsg = signedMsg.substring(64);
const decryptedHMAC = await cryptoEngine.signMessage(hashedPassword, encryptedMsg);
if (decryptedHMAC !== encryptedHMAC) {
// we have been raising the number of iterations in the hashing algorithm multiple times, so to support the old
// remember-me/autodecrypt links we need to try bringing the old hashes up to speed.
originalPassword = originalPassword || hashedPassword;
if (backwardCompatibleAttempt === 0) {
const updatedHashedPassword = await cryptoEngine.hashThirdRound(originalPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
if (backwardCompatibleAttempt === 1) {
let updatedHashedPassword = await cryptoEngine.hashSecondRound(originalPassword, salt);
updatedHashedPassword = await cryptoEngine.hashThirdRound(updatedHashedPassword, salt);
return decode(signedMsg, updatedHashedPassword, salt, backwardCompatibleAttempt + 1, originalPassword);
}
return { success: false, message: "Signature mismatch" };
}
return {
success: true,
decoded: await cryptoEngine.decrypt(encryptedMsg, hashedPassword),
};
}
exports.decode = decode;
return exports;
}
exports.init = init;
return exports;
})());
const decode = codec.init(cryptoEngine).decode;
/**
* Initialize the staticrypt module, that exposes functions callbable by the password_template.
*
* @param {{
* staticryptEncryptedMsgUniqueVariableName: string,
* isRememberEnabled: boolean,
* rememberDurationInDays: number,
* staticryptSaltUniqueVariableName: string,
* }} staticryptConfig - object of data that is stored on the password_template at encryption time.
*
* @param {{
* rememberExpirationKey: string,
* rememberPassphraseKey: string,
* replaceHtmlCallback: function,
* clearLocalStorageCallback: function,
* }} templateConfig - object of data that can be configured by a custom password_template.
*/
function init(staticryptConfig, templateConfig) {
const exports = {};
/**
* Decrypt our encrypted page, replace the whole HTML.
*
* @param {string} hashedPassword
* @returns {Promise<boolean>}
*/
async function decryptAndReplaceHtml(hashedPassword) {
const { staticryptEncryptedMsgUniqueVariableName, staticryptSaltUniqueVariableName } = staticryptConfig;
const { replaceHtmlCallback } = templateConfig;
const result = await decode(
staticryptEncryptedMsgUniqueVariableName,
hashedPassword,
staticryptSaltUniqueVariableName
);
if (!result.success) {
return false;
}
const plainHTML = result.decoded;
// if the user configured a callback call it, otherwise just replace the whole HTML
if (typeof replaceHtmlCallback === "function") {
replaceHtmlCallback(plainHTML);
} else {
document.write(plainHTML);
document.close();
}
return true;
}
/**
* Attempt to decrypt the page and replace the whole HTML.
*
* @param {string} password
* @param {boolean} isRememberChecked
*
* @returns {Promise<{isSuccessful: boolean, hashedPassword?: string}>} - we return an object, so that if we want to
* expose more information in the future we can do it without breaking the password_template
*/
async function handleDecryptionOfPage(password, isRememberChecked) {
const { staticryptSaltUniqueVariableName } = staticryptConfig;
// decrypt and replace the whole page
const hashedPassword = await cryptoEngine.hashPassword(password, staticryptSaltUniqueVariableName);
return handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked);
}
exports.handleDecryptionOfPage = handleDecryptionOfPage;
async function handleDecryptionOfPageFromHash(hashedPassword, isRememberChecked) {
const { isRememberEnabled, rememberDurationInDays } = staticryptConfig;
const { rememberExpirationKey, rememberPassphraseKey } = templateConfig;
const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword);
if (!isDecryptionSuccessful) {
return {
isSuccessful: false,
hashedPassword,
};
}
// remember the hashedPassword and set its expiration if necessary
if (isRememberEnabled && isRememberChecked) {
window.localStorage.setItem(rememberPassphraseKey, hashedPassword);
// set the expiration if the duration isn't 0 (meaning no expiration)
if (rememberDurationInDays > 0) {
window.localStorage.setItem(
rememberExpirationKey,
(new Date().getTime() + rememberDurationInDays * 24 * 60 * 60 * 1000).toString()
);
}
}
return {
isSuccessful: true,
hashedPassword,
};
}
exports.handleDecryptionOfPageFromHash = handleDecryptionOfPageFromHash;
/**
* Clear localstorage from staticrypt related values
*/
function clearLocalStorage() {
const { clearLocalStorageCallback, rememberExpirationKey, rememberPassphraseKey } = templateConfig;
if (typeof clearLocalStorageCallback === "function") {
clearLocalStorageCallback();
} else {
localStorage.removeItem(rememberPassphraseKey);
localStorage.removeItem(rememberExpirationKey);
}
}
async function handleDecryptOnLoad() {
let isSuccessful = await decryptOnLoadFromUrl();
if (!isSuccessful) {
isSuccessful = await decryptOnLoadFromRememberMe();
}
return { isSuccessful };
}
exports.handleDecryptOnLoad = handleDecryptOnLoad;
/**
* Clear storage if we are logging out
*
* @returns {boolean} - whether we logged out
*/
function logoutIfNeeded() {
const logoutKey = "staticrypt_logout";
// handle logout through query param
const queryParams = new URLSearchParams(window.location.search);
if (queryParams.has(logoutKey)) {
clearLocalStorage();
return true;
}
// handle logout through URL fragment
const hash = window.location.hash.substring(1);
if (hash.includes(logoutKey)) {
clearLocalStorage();
return true;
}
return false;
}
/**
* To be called on load: check if we want to try to decrypt and replace the HTML with the decrypted content, and
* try to do it if needed.
*
* @returns {Promise<boolean>} true if we derypted and replaced the whole page, false otherwise
*/
async function decryptOnLoadFromRememberMe() {
const { rememberDurationInDays } = staticryptConfig;
const { rememberExpirationKey, rememberPassphraseKey } = templateConfig;
// if we are login out, terminate
if (logoutIfNeeded()) {
return false;
}
// if there is expiration configured, check if we're not beyond the expiration
if (rememberDurationInDays && rememberDurationInDays > 0) {
const expiration = localStorage.getItem(rememberExpirationKey),
isExpired = expiration && new Date().getTime() > parseInt(expiration);
if (isExpired) {
clearLocalStorage();
return false;
}
}
const hashedPassword = localStorage.getItem(rememberPassphraseKey);
if (hashedPassword) {
// try to decrypt
const isDecryptionSuccessful = await decryptAndReplaceHtml(hashedPassword);
// if the decryption is unsuccessful the password might be wrong - silently clear the saved data and let
// the user fill the password form again
if (!isDecryptionSuccessful) {
clearLocalStorage();
return false;
}
return true;
}
return false;
}
async function decryptOnLoadFromUrl() {
const passwordKey = "staticrypt_pwd";
const rememberMeKey = "remember_me";
// try to get the password from the query param (for backward compatibility - we now want to avoid this method,
// since it sends the hashed password to the server which isn't needed)
const queryParams = new URLSearchParams(window.location.search);
const hashedPasswordQuery = queryParams.get(passwordKey);
const rememberMeQuery = queryParams.get(rememberMeKey);
const urlFragment = window.location.hash.substring(1);
// get the password from the url fragment
const hashedPasswordRegexMatch = urlFragment.match(new RegExp(passwordKey + "=([^&]*)"));
const hashedPasswordFragment = hashedPasswordRegexMatch ? hashedPasswordRegexMatch[1] : null;
const rememberMeFragment = urlFragment.includes(rememberMeKey);
const hashedPassword = hashedPasswordFragment || hashedPasswordQuery;
const rememberMe = rememberMeFragment || rememberMeQuery;
if (hashedPassword) {
return handleDecryptionOfPageFromHash(hashedPassword, rememberMe);
}
return false;
}
return exports;
}
exports.init = init;
return exports;
})());
;
const templateError = "template_error",
templateToggleAltShow = "template_toggle_show",
templateToggleAltHide = "template_toggle_hide",
isRememberEnabled = false,
staticryptConfig = {"staticryptEncryptedMsgUniqueVariableName":"2edcb32c2412004183c62c9fce9cc704935a6c0bd0e63996933e00345da238eb495f1418a540d884b315d54447b476eccec4f15d561ffda670f5ff6eeb66235cfa1512b90ae54ab1250c10f33f3e35055627b00164d35d10edf7024d823c61f094db08485db0e1351bec5ca2cbd66a604c18a2f6b8eafb841ded9651e3b429c5e289693cf7fe6fe2df9c377820315ce7","isRememberEnabled":false,"rememberDurationInDays":0,"staticryptSaltUniqueVariableName":"912233e7a39f3639c8fc29e835969b80"};
// you can edit these values to customize some of the behavior of StatiCrypt
const templateConfig = {
rememberExpirationKey: "staticrypt_expiration",
rememberPassphraseKey: "staticrypt_passphrase",
replaceHtmlCallback: null,
clearLocalStorageCallback: null,
};
// init the staticrypt engine
const staticrypt = staticryptInitiator.init(staticryptConfig, templateConfig);
// try to automatically decrypt on load if there is a saved password
window.onload = async function () {
const { isSuccessful } = await staticrypt.handleDecryptOnLoad();
// if we didn't decrypt anything on load, show the password prompt. Otherwise the content has already been
// replaced, no need to do anything
if (!isSuccessful) {
// hide loading screen
document.getElementById("staticrypt_loading").classList.add("hidden");
document.getElementById("staticrypt_content").classList.remove("hidden");
document.getElementById("staticrypt-password").focus();
// show the remember me checkbox
if (isRememberEnabled) {
document.getElementById("staticrypt-remember-label").classList.remove("hidden");
}
}
};
// toggle password visibility
const toggleIcon = document.querySelector(".staticrypt-toggle-password-visibility");
// these two icons are coming from FontAwesome
const imgSrcEyeClosed =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2NDAgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNS4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTM4LjggNS4xQzI4LjQtMy4xIDEzLjMtMS4yIDUuMSA5LjJTLTEuMiAzNC43IDkuMiA0Mi45bDU5MiA0NjRjMTAuNCA4LjIgMjUuNSA2LjMgMzMuNy00LjFzNi4zLTI1LjUtNC4xLTMzLjdMNTI1LjYgMzg2LjdjMzkuNi00MC42IDY2LjQtODYuMSA3OS45LTExOC40YzMuMy03LjkgMy4zLTE2LjcgMC0yNC42Yy0xNC45LTM1LjctNDYuMi04Ny43LTkzLTEzMS4xQzQ2NS41IDY4LjggNDAwLjggMzIgMzIwIDMyYy02OC4yIDAtMTI1IDI2LjMtMTY5LjMgNjAuOEwzOC44IDUuMXpNMjIzLjEgMTQ5LjVDMjQ4LjYgMTI2LjIgMjgyLjcgMTEyIDMyMCAxMTJjNzkuNSAwIDE0NCA2NC41IDE0NCAxNDRjMCAyNC45LTYuMyA0OC4zLTE3LjQgNjguN0w0MDggMjk0LjVjOC40LTE5LjMgMTAuNi00MS40IDQuOC02My4zYy0xMS4xLTQxLjUtNDcuOC02OS40LTg4LjYtNzEuMWMtNS44LS4yLTkuMiA2LjEtNy40IDExLjdjMi4xIDYuNCAzLjMgMTMuMiAzLjMgMjAuM2MwIDEwLjItMi40IDE5LjgtNi42IDI4LjNsLTkwLjMtNzAuOHpNMzczIDM4OS45Yy0xNi40IDYuNS0zNC4zIDEwLjEtNTMgMTAuMWMtNzkuNSAwLTE0NC02NC41LTE0NC0xNDRjMC02LjkgLjUtMTMuNiAxLjQtMjAuMkw4My4xIDE2MS41QzYwLjMgMTkxLjIgNDQgMjIwLjggMzQuNSAyNDMuN2MtMy4zIDcuOS0zLjMgMTYuNyAwIDI0LjZjMTQuOSAzNS43IDQ2LjIgODcuNyA5MyAxMzEuMUMxNzQuNSA0NDMuMiAyMzkuMiA0ODAgMzIwIDQ4MGM0Ny44IDAgODkuOS0xMi45IDEyNi4yLTMyLjVMMzczIDM4OS45eiIvPjwvc3ZnPg==";
const imgSrcEyeOpened =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1NzYgNTEyIj48IS0tIUZvbnQgQXdlc29tZSBGcmVlIDYuNS4yIGJ5IEBmb250YXdlc29tZSAtIGh0dHBzOi8vZm9udGF3ZXNvbWUuY29tIExpY2Vuc2UgLSBodHRwczovL2ZvbnRhd2Vzb21lLmNvbS9saWNlbnNlL2ZyZWUgQ29weXJpZ2h0IDIwMjQgRm9udGljb25zLCBJbmMuLS0+PHBhdGggZD0iTTI4OCAzMmMtODAuOCAwLTE0NS41IDM2LjgtMTkyLjYgODAuNkM0OC42IDE1NiAxNy4zIDIwOCAyLjUgMjQzLjdjLTMuMyA3LjktMy4zIDE2LjcgMCAyNC42QzE3LjMgMzA0IDQ4LjYgMzU2IDk1LjQgMzk5LjRDMTQyLjUgNDQzLjIgMjA3LjIgNDgwIDI4OCA0ODBzMTQ1LjUtMzYuOCAxOTIuNi04MC42YzQ2LjgtNDMuNSA3OC4xLTk1LjQgOTMtMTMxLjFjMy4zLTcuOSAzLjMtMTYuNyAwLTI0LjZjLTE0LjktMzUuNy00Ni4yLTg3LjctOTMtMTMxLjFDNDMzLjUgNjguOCAzNjguOCAzMiAyODggMzJ6TTE0NCAyNTZhMTQ0IDE0NCAwIDEgMSAyODggMCAxNDQgMTQ0IDAgMSAxIC0yODggMHptMTQ0LTY0YzAgMzUuMy0yOC43IDY0LTY0IDY0Yy03LjEgMC0xMy45LTEuMi0yMC4zLTMuM2MtNS41LTEuOC0xMS45IDEuNi0xMS43IDcuNGMuMyA2LjkgMS4zIDEzLjggMy4yIDIwLjdjMTMuNyA1MS4yIDY2LjQgODEuNiAxMTcuNiA2Ny45czgxLjYtNjYuNCA2Ny45LTExNy42Yy0xMS4xLTQxLjUtNDcuOC02OS40LTg4LjYtNzEuMWMtNS44LS4yLTkuMiA2LjEtNy40IDExLjdjMi4xIDYuNCAzLjMgMTMuMiAzLjMgMjAuM3oiLz48L3N2Zz4=";
toggleIcon.addEventListener("click", function () {
const passwordInput = document.getElementById("staticrypt-password");
if (passwordInput.type === "password") {
passwordInput.type = "text";
toggleIcon.src = imgSrcEyeOpened;
toggleIcon.alt = templateToggleAltHide;
toggleIcon.title = templateToggleAltHide;
} else {
passwordInput.type = "password";
toggleIcon.src = imgSrcEyeClosed;
toggleIcon.alt = templateToggleAltShow;
toggleIcon.title = templateToggleAltShow;
}
});
// handle password form submission
document.getElementById("staticrypt-form").addEventListener("submit", async function (e) {
e.preventDefault();
const password = document.getElementById("staticrypt-password").value,
isRememberChecked = document.getElementById("staticrypt-remember").checked;
const { isSuccessful } = await staticrypt.handleDecryptionOfPage(password, isRememberChecked);
if (!isSuccessful) {
alert(templateError);
}
});
</script>
</body></html>