mirror of
https://github.com/mprimi/portable-secret.git
synced 2026-01-13 09:38:07 -05:00
297 lines
30 KiB
HTML
297 lines
30 KiB
HTML
<!DOCTYPE html>
|
|
<!--
|
|
Portable Secret generated with https://mprimi.github.io/portable-secret/
|
|
|
|
This file is self-contained, it embeds an encrypted payload.
|
|
It uses your browser's cryptograpy APIs to decrypt it, if you know the password.
|
|
-->
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<style>
|
|
body {
|
|
background-color: floralwhite;
|
|
font-size: large;
|
|
margin: 50px;
|
|
}
|
|
|
|
div {
|
|
margin: 5px;
|
|
}
|
|
|
|
pre {
|
|
padding: 5px;
|
|
white-space: pre-wrap;
|
|
word-break: keep-all;
|
|
}
|
|
|
|
button {
|
|
font-size: large;
|
|
padding: 12px 20px;
|
|
}
|
|
|
|
input {
|
|
font-family: monospace;
|
|
}
|
|
|
|
textarea {
|
|
font-family: monospace;
|
|
}
|
|
|
|
.decrypted {
|
|
background-color: palegreen;
|
|
border: 2px dotted forestgreen;
|
|
}
|
|
|
|
.hint {
|
|
background-color: lavender;
|
|
border: 2px dashed black;
|
|
}
|
|
|
|
/*
|
|
pre.decrypted {
|
|
}
|
|
*/
|
|
|
|
img.decrypted {
|
|
padding: 12px 20px;
|
|
}
|
|
|
|
a.decrypted {
|
|
font-size: xx-large;
|
|
}
|
|
|
|
input.password_input {
|
|
font-size: large;
|
|
padding: 12px 20px;
|
|
}
|
|
</style>
|
|
<script>
|
|
// Display the encryption inputs on the page (invoked during body onload)
|
|
async function init() {
|
|
document.getElementById("secret_type").innerHTML = secretType
|
|
document.getElementById("salt").setAttribute("value", saltHex)
|
|
document.getElementById("iv").setAttribute("value", ivHex)
|
|
document.getElementById("cipher").innerHTML = cipherHex
|
|
|
|
if (secretType == 'file') {
|
|
document.getElementById("target_file").innerHTML = `Download file.${secretExt}`
|
|
}
|
|
|
|
document.getElementById("password").addEventListener("keydown", (event) => {
|
|
// Decrypt when the user hits the Enter key after entering their password.
|
|
if (event.key === "Enter") {
|
|
decrypt();
|
|
}
|
|
})
|
|
}
|
|
|
|
// Invoked when the 'Decrypt' button is pressed
|
|
async function decrypt() {
|
|
try {
|
|
setMessage("Generating key from password...")
|
|
|
|
// Load salt, convert hex string to byte array
|
|
let salt = hexStringToBytes(saltHex)
|
|
if (salt.length != saltSize) {
|
|
throw new Error(`Unexpected salt size: ${salt.length}`)
|
|
}
|
|
|
|
// Load IV, convert hex string to byte array
|
|
let iv = hexStringToBytes(ivHex)
|
|
if (iv.length != blockSize) {
|
|
throw new Error(`Unexpected IV size: ${iv.length}`)
|
|
}
|
|
|
|
// Load password, as byte array
|
|
let password = new TextEncoder().encode(document.getElementById("password").value)
|
|
if (password.length == 0) {
|
|
throw new Error(`Empty password`)
|
|
}
|
|
|
|
// Wrap password into a Key object, as required by cryptography APIs
|
|
let passwordKey = await window.crypto.subtle.importKey(
|
|
"raw", // Array of bytes
|
|
password,
|
|
{name: "PBKDF2"}, // What algorithm uses the key
|
|
false, // Cannot be extracted
|
|
["deriveKey"] // What the key is used for
|
|
)
|
|
|
|
// Derive a key from the password, using PBKDF2
|
|
let key = await window.crypto.subtle.deriveKey(
|
|
{
|
|
name: "PBKDF2", // https://en.wikipedia.org/wiki/PBKDF2
|
|
salt: salt,
|
|
iterations: iterations,
|
|
hash: "SHA-1", // As per standard v2.0
|
|
},
|
|
passwordKey, // Wrapped password
|
|
{
|
|
name: "AES-GCM", // What algorithm uses the key
|
|
length: keySize * 8, // Key bitsize
|
|
},
|
|
false, // Cannot be extracted
|
|
["decrypt"] // What the derived key is used for
|
|
)
|
|
|
|
setMessage("Decrypting...")
|
|
|
|
// Load ciphertext, convert hex string to byte array
|
|
let cipher = hexStringToBytes(cipherHex)
|
|
|
|
// Decrypt with AES-GCM
|
|
// https://en.wikipedia.org/wiki/Galois/Counter_Mode
|
|
let decryptedBuffer = await window.crypto.subtle.decrypt(
|
|
{
|
|
name: "AES-GCM", // Name of block cipher algorithm
|
|
iv: iv, // Initialization vector
|
|
},
|
|
key, // Derived key
|
|
cipher // Ciphertext
|
|
)
|
|
|
|
// Remove padding (added as necessary for block cipher)
|
|
// https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7
|
|
decrypted = removePadding(new Uint8Array(decryptedBuffer))
|
|
|
|
// Render decrypted payload on the page
|
|
if (secretType == "message") {
|
|
// Decode bytes to UTF-8
|
|
plainText = new TextDecoder().decode(decrypted)
|
|
// Display the plaintext on the page
|
|
document.getElementById("target_text").innerHTML = plainText
|
|
document.getElementById("text_output_div").hidden = false
|
|
} else if (secretType == "image") {
|
|
// Transform image to base64 string
|
|
b64Data = btoa(decrypted.reduce((data, byte) => data + String.fromCharCode(byte), ''))
|
|
// Create 'data' URI
|
|
// https://en.wikipedia.org/wiki/Data_URI_scheme
|
|
const imageData = `data:image/${secretExt};base64,${b64Data}`
|
|
// Display image inline
|
|
document.getElementById("target_image").setAttribute("src", imageData)
|
|
document.getElementById("image_output_div").hidden = false
|
|
} else if (secretType == "file") {
|
|
// Transform image to base64 string
|
|
b64Data = btoa(decrypted.reduce((data, byte) => data + String.fromCharCode(byte), ''))
|
|
// Create 'data' URI
|
|
// https://en.wikipedia.org/wiki/Data_URI_scheme
|
|
const fileData = `data:application/octet-stream;base64,${b64Data}`
|
|
// Activate download link
|
|
document.getElementById("target_file").setAttribute("href", fileData)
|
|
document.getElementById("target_file").setAttribute("download", `file.${secretExt}`)
|
|
document.getElementById("file_output_div").hidden = false
|
|
} else {
|
|
throw new Error(`Unknown secret type: ${secretType}`)
|
|
}
|
|
|
|
setMessage("Decrypted successfully")
|
|
|
|
} catch (err) {
|
|
// TODO better handle failing promises
|
|
setMessage(`Decryption failed: ${err}`)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Transform hexadecimal string to Uint8Array
|
|
function hexStringToBytes(input) {
|
|
for (var bytes = [], c = 0; c < input.length; c += 2) {
|
|
bytes.push(parseInt(input.substr(c, 2), 16));
|
|
}
|
|
return Uint8Array.from(bytes);
|
|
}
|
|
|
|
// The cleartext input must be padded to a multiple of the block size
|
|
// for encryption. This function removes the padding, expected to be
|
|
// compatible with PKCS#7 described in RFC 5652.
|
|
// https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS#5_and_PKCS#7
|
|
function removePadding(input) {
|
|
// Last byte is the amount of padding
|
|
padAmount = input[input.length-1]
|
|
unpaddedSize = input.length - padAmount
|
|
return input.slice(0, unpaddedSize)
|
|
}
|
|
|
|
// Update page with status of decryption
|
|
function setMessage(msg) {
|
|
document.getElementById("errormsg").innerHTML = msg
|
|
}
|
|
</script>
|
|
</head>
|
|
<body onload="init()">
|
|
|
|
<h1>This page contains a secret <span id="secret_type"></span></h1>
|
|
<h2>Enter the password to decrypt it</h2>
|
|
<h3>Created with <a href="https://mprimi.github.io/portable-secret/">Portable Secret</a></h3>
|
|
|
|
<p>
|
|
This file contains a secret (message, file, or image) that can be recovered if you know the password.<br>
|
|
The secret can be decrypted without an internet connection, this file has no dependencies and no data leaves the browser window.
|
|
</p>
|
|
|
|
<div>
|
|
<h4>Password hint:</h4>
|
|
<pre class="hint">A yellow edible fruit with elongated shape. 6 letters, all lowercase
|
|
|
|
(N.B. this is a _weak_ password. But for this example it will do)
|
|
</pre>
|
|
</div>
|
|
|
|
<div>
|
|
<h4>Password:</h4>
|
|
<input type="text" id="password" placeholder="See hint above" class="password_input" required>
|
|
</div>
|
|
|
|
<div>
|
|
<button type="button" onclick='decrypt()'>Decrypt</button>
|
|
<span id="errormsg"></span>
|
|
</div>
|
|
|
|
<div id="text_output_div" hidden>
|
|
<pre id="target_text" class="decrypted"></pre>
|
|
</div>
|
|
|
|
<div id="image_output_div" hidden>
|
|
<img id="target_image" class="decrypted">
|
|
</div>
|
|
|
|
<div id="file_output_div" hidden>
|
|
<a id="target_file" class="decrypted">Download</a>
|
|
</div>
|
|
|
|
<details>
|
|
<summary>Details</summary>
|
|
|
|
These are decryption inputs, that can be safely transmitted in the clear.
|
|
Without the correct password, they are useless.
|
|
|
|
<div>
|
|
Salt:
|
|
<input type="text" id="salt" value="" readonly>
|
|
</div>
|
|
|
|
<div>
|
|
IV:
|
|
<input type="text" id="iv" value="" readonly>
|
|
</div>
|
|
|
|
<div>
|
|
Ciphertext:<br>
|
|
<textarea rows="8" cols="80" id="cipher" readonly></textarea>
|
|
</div>
|
|
</details>
|
|
</body>
|
|
<script>
|
|
const secretType = "image"
|
|
const secretExt = "jpeg"
|
|
const saltSize = 16 // bytes
|
|
const blockSize = 16 // bytes
|
|
const keySize = 32 // bytes
|
|
const iterations = 1000000
|
|
const saltHex = "d08ba7371bc62336ecdb48c983b5e7f8"
|
|
const ivHex = "f9171803759e32668ed533ae646bf8c0"
|
|
const cipherHex = "34ba6964b732ee94b10efd036421df482ab19c1d5169fbcf395968cafdc3cd4657920d9f8e54db68730fa0b05389f936f044cc50731cba091415b7d481c61b146ab5d39621e8b49249d69564f560f71e4beeed7769046fa895fc00d6dba90b99ee4dd3008162e77d4f5eb1d2c9064a79e8d8f9538d86adb7c058bc89e5de622d5fdc7147a0bf48433016687d3ca5c3bb6b0f93b1536740f818a98192c1548b0a6c993b4fa4c2dabe02e6c73e8e8cc62846d9662d1b44edf8f589a97cdc0a8fb75235691c540d1b296a9c74b9a0e0c4f3b0c51a27c692814b24e5acabcedb5ab4620275f7b5abfae52bc058eef4d30af128b4c9623040ca9193ca19b1674ab1860ed49a16a34aa8032fce28a44861c8edd52391bdab1a2aeb9220f1b10ac6670e0d578720a34892a2edea983b8ac673c623f3ea51811442de37e376d070e482c1e826d5d377e7bd8cbcd717a3859cddb99e5cc1f2aabbe6f599cf6ea1f85d881b88c537646f309ce037f169c7394e54eff3c0ceccbeb5426d3caacc2123cb0a3e7943e7bc1122956b7127a6b0427ab38411e031670dbe08b8139d9887a971e44a04693290a0be683a13697afd170c08b8a822a6206a314374555070270afa2df60de16b799028fe83f819d42b4a1964911819d755f91472e2cf5fde5f251d4adeacc0f624ab8946bbd63b16d8a3f3e0e5223b80cb28d31227c5d170c67986e77250e205dc0f6ef7735a870cfd73f6e26061b9e0a457803713dfa6bf59ef14962df1b5c22cecd02ab70bfdbff7951803ad8157c85b944f8b522d859a50c963b91a27b1eca9ffe45018bdca1d3057f96093c49e913faa96fa54f3eabef7439b2ecdb6f258cb818cea670a989fe7c50cb8ba75d2bdeaf9ddf334e231b9191afbc7c9ac5cd83c3da818902cc4be47b3a4dd5759df01ca5907d60189dea45db1505cd19f4bcb79db6418ee82cfcb1dbc65676eb2d2b62d435a5c7d1a6e4c9743e2c1abbf2280775d276875f8e6e824a1a4f4d0810a2f43f5e8ce8ea29b0632c22c5d25ae9cb18a4f6fe8a465a8256f783cac2148ad3d1917da993cf396270b3d36781d93cfef4810cd0d4126c0d19f0c9724670a7edec5ef5117084f7e8b2a7d6f193c1091ac0604bd93147d156d66d109b10b1377cf0486629e585229f07ce0e95764cbd48f94b72ccad217d30b537af360f9d2c601537c711ad7f6374cbfe99189a21ff4ff24aa9c3751c5d9be5da17701d647e4b1b79867023c75b7a83fd8f99b90108256406c233cdecb50be6eb63164d3d00966c77d5a2676a71e0667900c79dc20857fa64ef6ed8533477f434a83e0b6a79d67a489d49661382274080bc9241324c50f7e733dc5bd56011e06254ccdab6c91fef34aeada96b3c4e8c663b8d936ae9930dd3f2f42fe05c813cf324725f7a1b8039a1d111a5551fade18fec58c3c203d240d8d64bd6b48da2c67d75eadfe3d51f22fdad7ec85c8670166d03e04cbb7a2a4e1ed881ba942d3856a4b5883ebb644a325a9d0cf3f0bf95ab15f8163931090e5df424f3a6f434016b409e12fb95d79b6c16690885b8a555b57e66b1e6a1cc448f2caab16ea543e0e6540788b8bc8731e5db18dd1c70858f23ef586aa7e8bbd83cc2248a277a289f36f536dceb2fe6f5efa91afdcc7c9bb91910bdcb13914bed387181d6638caef1a64ec9570fb0a122532223d09d9798cf9e7e183790e757e5b2dc0d17e9133f1b1458ed904d6d8247be4bcb72041cb86e56c4ba1e52bfc3d03ef09eba0b7ecfc0775ed5952f06f9469249af8cf20cc8faedee77d85c019cc2807c31d7ee7d9f7f68c8fd85514938a3de6cb9c59ef3e23a2fc06ab3bf1b45a16dd6736056377e87fb502d3c7e40ea2d0e6c6211f89a7dd21a8fcb687908ffb3ada5d072c7cb5e5d1c5add1174303f428b5002988a73f2adf507d977cadc371d806bfceeb20ffcc46e4a50cc24254ef075ecdce37b1bdc59e60b92bf7cb783f414910d6166c4c417fbcf6c2dcb41b4bb2ebfb1b06f32687703e6a80e8a8dcb8288eff94a689d1dc2079fe89835ac93d33f4fe8fc7433d4a44851cd24bad06e941eacbecc0f40e2e1c8379a1c76bdc1de7d87ece889dc4fcba10a05d3b7dfcfcff9ee76e85f89cc87f090454b1bb54ff2660915ec92e17f3cd0aaa90b3a69fd59bd1c2331edc77dc72d697898e0e19aa53c260e1e9f2b7e6eb24de64737a2ade0c746b6f9ff372752debb85062bbbcec43c39b49efe816959f12515230bc28aad124196b5d0b251ea80a6f67eeae0734891ee22a3209fe85ac7cc80ec329f548188f247bc222f99c142c703f261d216811fba7b5113d316b3cd9b47655a0e9765ddb4d79d9268f12d58b3c1a814bd379b96fe67413763f51860fa67d2d0b2a088666826df3e6a12c9b6d79ec5e159d747ef41aee050ac67d20a20000df175612d65ca4802361814c530b25bff501deb872c12443853abe5ba8efaba9a1b98fd67c81623e7e09aa2737e7bcc8316d986521ca29936456766ed8879275fc41a847c1b610ecef92c7a2b2e967762e96e4b7ee0d24952573b42a6f835c9e2299b0bd218e3b6853ca44aafc76585d7c754bde3076963fd142d943a475418bdea5efb75415eb4921427b876396a0c5a9052072f0db8fce066e403e7a16128a31d12fd09304f8ada467c4bcac31972fef89d60e90dc82a16347e9b6c8d66050b322e06f2e6adb30c56d0d326f49d8999b4a46e62f7220f8e06dfecc9568575aee32d0847c5c1bc7ed06348c0cf5f620e403c9e6804edf5b6a13754c3664ef9e65a5140069a22a89f12cb286da0b18865be4b14422721c783398bcfcc52437badcd4c9192fabdea61d3c8bb84c6eb499707644723ff635b03b2aff5e862f699146837eb502e6bacf2a576aeef4f47694c8e88a66ba967086689f3d851307b493c4ffcfd079de17afee5f334ff99bb4c9733b7593417d12dd87876b9d0f412442e4760e0a69f16f483cf96cf5924b3ed8d4aceff97e318f4a68087cca4c372fffd79eb3231661c282aa9057db22675aadee38c21ba07780de01e70ca780b28149b15d8fd00a34cf9dbfb9b159c840cff9bcb60f49a4b147c0d5bf16ad8cf038c07f6a6cf587ef6ad2d160ed5a28cc85957f8d64d6769c600f1385eef85330c174f321d9d84c7deb04b564b15e8a3831e828ab8dfbcf487a264af5fee683b7bbfc3462a88d0d7eb64258a76a04de47efaf15cd0bf7342b220298ed3c48c99540804e6979d3f48597c41e4efe13040450188252cc306828f47c066aed3fd431381159857c3990cd7562cdba223377a824b3529c558e2cd552fe964ceda2f84e07bdc67d16c31faac447ea92a4d26045c474df07255a3c3fbe96161d5ded364da102c5c8d827fbd0b33a99a08dcd6998458a28a1df35099d21bfa2136586fa7614f2f7bdbd50875abb5f3fed0fb84696da56032d16e8494c89c44bc3eb65dcb6197021a828312dc68f2afadfd2603586c09655c92c3345d2278c60b221af04cfda93c149addd93a7540b2f6edffae69bfedc7ad508f08005be470b3df8c3a51d0459e73710cc3c9ef2cb8f30a79ac856f6cdc631ec4494f570bfb921b4bf93ad13cce6d470a0e066c58580c897273653cf006553f03866226bdac8d19d5a33c62194ebdfeb3be121c949d72a573f65431fb08ed6f6061f5fa2eddcf628dbbcb995331ff0336403eb19d05b2cc4046c77781d8648246c23c84ddba4d4e26f9962917b61eaa7e20dea98d3ffe43f63a21ac9d576b108b0a8a25a41c75a84ce4f982ca2d8dee6f5952c36e9c2fe32885dc0d57da05035cf0aaac4ebf1e07336578c9427c602d0abfbe9e15e251949e971594c7baea07983d117dbda4b77a813ec6a077a07a37354c26a2d1e2c9e6811ab489252179b2aecf0edd33c5b1ac268114d48cbb5082cdb80a337453a1d9b40bdf18a00f5f02ffdae67584f468f2db8302dae98d9a1c208d5807c3d506d30b56d902f8c561c5179d76108d7e9777614f569aa3c6e87b5317d4ad3b26c0dd7e2485fcd2939480044956741365ae046928511da321e6d224088f91fb9c2d782814a49b58f7b4552b02d7453c86daad18a5c5ca8060ab6b7ee8aa80d69cf0c2cdd650fae781335445966b34471282779f3da8cf08d9d623702beca5304f92a06e2bdc580ed56f1cad7ee5eeb5cc9ab5e795f99a42ea87e536d5e2969582a54634caf8e086691a3e54d013ea277ed8ea6fe00e2e86b78ab95cd8e14df41583b78d881c9fce82b70071f784458f86869b0790d95b210805e8f7c8d41fc9c3d06f88d6ea98e6e80b9dc9870f78028ece5a31d2334ceae8d87e4de37f32e2e5bf5f91abca027f80cdebd61dbc719f6651123f5ccc643c51eed5a95d8129a0ef752d39142e8beb28d58ce5859ea66dd47f0ac77aad881565895c7c6135ccb71bda2a5e8aa05cc55f436663ffef3ca62764ac90aeffbd353fce323157d9126dcd30503724117359845b0ea4283b9f42ab8373889144838e320da919d903e05976153c7743608668b5f478fa3c6b7347abac4d7fc3443c1b45c32f4681ac6eb61bb46e6c49cc4d427f22beaba5f21d7c3eb77e47c05eb7d583bbf94f8de35c82ed7e63fb872178ce0891572a4917f5accf3869f800fd387fbe38e21733f4ebcc507c1e8ae8ec0756283d559e105d0d397ea6a84d9817db1c81ead43feaa9ab1161233c9fad65081b3dc5c412649e7725586a3cde751b694e5656695ed575894c3fbd37ede7d7375abeedef4d8266f4b07f55686336da93f6d399e7b4d68ada3145cefca383bf65a4d5b6f693fe60a4b6e49b5de5d8042d961eeea3bd25916ade99442663d6c1743d25f3011c9dd4d3fd118dab496f7ff1ba29b14597853336633c3fe80ee3f6b12f20ab9160e60b3d7cc553a4b495233f8adf1754e7144d12d9a42227d8dd7d6ce5795be8df2b67b888ff30459dd422003260c8305b916cf8cb0c9ac4e5712ef40fc21b262b97aa1f15d7b364f6b0904a3cce432f91c2938449f22d9d298e6698b2c7dddb180999c8a8ba324555972372c0f9d045589b74bafe68d1a30df93e3a656f72d0078eeedf5f64d3eca9d8ba1bd8c2d5069ad2621f3fff332e57e064801da7288f7e5408b4182a641ba6106831d8e4fc5c9f8a5976cdd8089db0a7f2db8c5acb0276e3d6c4edeca85f7dbaf566bfc4405ac938f3e949e073163e173a19da37964e780b2d3063d2091f60e769a8aa679c6928f6cbd60250752eee5b03757fe9787c702010e2e72cc9a636516d4827e0d9706300967cb29fe3a94552d48e842a5c9b619bd8a1e5e3eac0436dc59d464b44be7c0445092694fb1f5979583140c1fdb7ac74b45f06793c656d5279bea992fecf87c8fd1f7ba431821eb2a87e36d0560def9e0687f8bae3ab74073052869168649ef4bad9158ef7bb9a461c51ff3e4e26bcfb70d95bb0fd0d048d2d9b769065f8e69e101f71d41b91984bda0a436429f89e1fcc2e50a5eba9aa4c9fbd4e61026a1847f38a2a34d4eebd92bd4f43babec1d6035cab2ec7e0ad0b9ebfe14a152db5c48dc81a22dfa053e9a0c7d8634c38a6117112409693f3bb9927b2ca05a2638f329a56edaef7258ef3692ae31a58c774cc1540a26c41e83699399cf5bdd8bae8b2c65aabc2cfd40c6558d7caf2fbd7ec11e7a5ae62c31548e90392fd9e70713fc07596da3767357d95377f0b20e5cd94d1ca3557cd21da70f5a47bf207f503fa9d1a4464675829373754875a2c7c7531515361f1e2e295d18a538eecb037d4a454339cba27aea8419ba4db29384f2738011362204ece21cc98a3efed01754db7e43ce25a7b02c28c3bec31d5f54401043847f726acd11996e82f8a5a2bbb74f45265522265d915f617727b0097a4025a05b54d63496be53ae5c4dd068dd93e55058029d6aab11584a17ea8b09ef6d75be6ff41084b03a24abdffc4def91cd86f44690a9c88340854edebd1af39ced11394f5bbce11cf496d5cd0d127c334f4147470cecdb5dfe345779a1d7b730041bd380eda67dcc4c9b563c29af7f372303838a1a1a51bcb2f451f6ecc1a5ae4a9b03f1d23565f88330489c171f041b79af184ce45dbeaf47efc5f38909e13e185d96e65519dcf1cc3287466b3fd18925cf9701834911a374ee47f6a1f6aafdf8cadeb755ab48818ec2554fd01944620ec88abde8f3b09c5a3ec143f6551da28ff1a50f50773771038fb1e1971b0f45e4b3fde3ba20b965bf39b9fabccd6b7855382fd3887e2dde38ed17704db69f9d37d0c416df1e30319afe946138f1bfef1f2dec97caf15aa01f723cf800b20962d523d1e8ec43b3072e355fb7bcbf0f8b76357187f3a2e9bbe6eb86fd0744e8e3f95ef964669f0b8243681ddf363758463a1108489564ba9c184ee5e0d4f6be7288b6dade7588804cf988178c5ff921fe6fd514a3005ddc0c07216be9558afd5d9e71a592482699013b9ebc5cabc7ea0da5618bae53e0262e72b53399de428a1114713a5381e1e5566c2a71327023b48105531e871c284a946e1c73fb2c250428f3aef385fd5816c2480501ba54175c7f61c1e2a0b4577c712c8d3e1e2a456179da65f457737c41667b513d9a58b66205323b250ff3cf49a77d00c6b6d0708dbb65647bd0f564638eb1567525207c846c744e386baf0867199d8a488b666baa994607e372ae1ea9c97d712adf1d23675e08d5b69451357080439d593a622d6845ede035c8e1579aacb42445ee96081f39ff985fb3ccdfd98c801577026c2aa8874a8b424518d0a7953196ae0d604b1f71728290946d7ae1127e9c650ed9d9a69706408804ad9ec749fdc4204e83777bfa04fae802550043340e257dfea547eeb14e0912935604f7b2de8322d826b8189a32d0cdf705973ebb575884055c947d28e360f88875d924987d62473e3469e0340692538e41467b7c563d034a90de6e574b9172a093fb4738a3be370538222d2aaa2038843a3b1cce6f479e99d30e4dc777216412d64b66ba508deef3142806e7f78e7fc2c3e28cbf2a4a646819abc7a3e166e0eebe670476489034f825d05e966a452796a01ac2d6aeaaa4abacbd0bef870e97a002f494acf096ab092706bd4cb47c3b098873b56e1d69d527a0d60be3d58fbd97f0e154f489fbe2a0bbed924602fbead4d62b54c3ad1a9a30d16c13f463df977d17594a63411cd163203d0709a871cdf3cf76a331383f9c1cb8998c64811cd9b68f2d8e818b3eee69a27afaeac65348b0c89892191accca21a4d0fdf9c67554bfc3c7f01c88d17f85d84b894d7c93e8ae2d2c0e76c474a9819ef189ef52c50c5e974f7b5de4670c941319be0dc7507233ac211100c9e8fc7b592741b8cdb1e945cc479685c7496d928b15710bd09f7a4dc3d2b6017ce9113cd4c8568392bd502c59618527d8ec3a8e8b9bed442acc4a6cf201e39e1704afe9addb4ae6d548034934dd42a1be6c4f86dec45a20640b172da0bac7b4e0d218bb24659107e6bf6d20cf9fbf3dffa7a15491ecd09802dfba18427877a2436cf7ccadf0537526b6dce523c4bc8bedfb7f819c2177e80c9b4cde6f282fb96a555f93cec6d5e53ad5b2f8a00b2c8a3c1806e99c5e6c460510622704ed5f4224ec1a790252aff202da86d567bef98c107678e34264d6a7698933a9c0b04f43a4af9ff6e8a7e2441c721accca102f7741310a4384ff33c31523801eeb0db5dcefc9760768187fe151ac1e39e334bf106bf6cf157df5965a48ef0d0b2c71ac052e3018368ad08a92805d53163be40540f93872550dc61974d7b9d21671b96dd13d1db89372a9847be8d0dbade639d95db61bdb14c5086c829785aab11a0e86f25884f600672b96880239f49a90fc714dc6a2bb41364c2842a068239651e4516c64e5d8e47cba8b1351b69a084304e812d8f2c441a95a5526bc4ea1c0d6274251aff21d2143a89e59599715f941f6484c49d10162cf3d73d5a4ffc358bb3c35f6e80e831a2877bf73c44084bfce0f533b47495d1f03a74cd5121792def2621dc0281e9b4ac99443d98162d3a0b7edb35cf847ea561eae01ca114d7556e7858ef3c93b9bdbc6a84aa7b81d4aa2c3a85f8f899e54f892db5cc438ed997020f72496c496304408e6038147a0302defd5d63b5161042006a68fd28949c899f1f2b7b3937d93952f8bf28569cedcb6c1a74adf84b87cfd05ec9f47830702d9bf4e470e97dc2af40e6aefdec9130b632fa5555cd016fa68b6b81d7ecfc493b8442e15bfa2125e19c2d7dab2319af56c6baa2a8b069bc8ff2911e25266b04d5882f0f761b2daceacf57ea7a9e27b6673de99af7c312ed552c9df01cc6e55012d0b260ab70e5c8bb9e087803875a823d9754172be69d1adf63fe13d3fe376b8a3f4c297a8985385a868b8e430c6bd6d6e394fd0d7846270803214d4e4edffff49714a9dd78c6f99b18f7d79e0863d401ae20f29de783d428c29b7c668e0c08cff386d3f8fbf55a4219234e9bc26abc05efc3797265f4d716f1e5f1c00d1c325ede2903f015ab5a3104f13f716360275cedbdfeaa58d4d0515431d527819cbc1f586ffc1ac0b397c0350a55364d83d6ec361d7a5d1b10b8c3edd0b7729d7a488550d86c9aea83241710e09b1c39bdd7d213e3c9ed67b3bf779bec31a6c37fb29bb67a37e7f2e1c8497aa4d7974f28b09208f6b24ddc1ba0bbdf608c0d3b66e7e5bfaaa093dc856547f30ebc1160a37351d93766ac2bb3bb4f3046aaa17c9e90b9ed6854136eeac960b08642c0d9c871cc37dfc96c2ec52f435e4ca54c095ce881a345b4c3aba18d0b51920b09768bcb23a4c5838abf1ba68dd9964479263904415c6d4a3423e3810c546c6e4ca3e5283d38de54581f816d238241f0b8b85278c7e347b27ab3b0078fa2c6f43ee0e717507e6f273c2c3d5191929f4cfeb02b8e784bf4cbcdcfff0e86979529a0e069b4a061c6cea5511c7a16ba5870250072fc7a04dabbf9c378ed1723a593e5ffb2f98f03696183ac9890b109445902ba6ce084572dad62416da1337c579e6d44e6614f680ccfa5501dbae08013457bdd36cf4d8e167fcc5129de25d554028fcc613b6b17ff78da315057870df1fcc44932d974f3832afb5ba1a97e55784e3adabb1488c10c0dc0d4502545692cad469f6f0a1d241c490b744a59cf9d5a1b6320f8d51ecb2181ef2c4496056ebfb762e93ad1696bb23d12f652bd7b844a66c3afa27ff54040e2f79b3c059ccaae2db1a45a9a2bffa9bc768a91d28b9e8a8e7263d2192d33028ebc2b3dc5d2a52cc12a81577dea5e3c954192038a6eea635cc45a09389d2c6c607ba7a6299ac58fd03df035fdab22da9e14eebb61da4d51f968387cfb4401a8e29671e8d80837c063a85a179ff86564188eee6f6639e8455bb515ef5816d9bfdffb67702ff5927271c493c2d7a93c99376cd97ee545ff0e69c3f23adb603c2de21fa08c61153552cd568b42bb4b67cbc381dca3c06c2061bdcd28f4cf01f96080387391743f44d96363e4e3d48c9fe174a15cc15d91dff7775deafd21cfc5017a05797d1c1de67892f3900687db684172c80eb4595fbf553b7f6fe6bc7c57c871ed6ddc4dbda64c12a0f487f2209a3e073bef45ce20a6e809d1c62a6589d5554dd3a9dc7ff0fd25ef14513ef63a6c2dc3bce92946daa41943e2c75a0ca3ff9ac62815fef10c8d0bad84d0dc975296f6c15a36d279402d5e9b203453815f2d2eb4d1d595c30be33d871af857fb14c5c21350958eede24be424455dd30c126a18241865a7576c1894aef71c1078b1d8c88ab586615e71de23bdb1b691424a666413d99a6076dea8356039188c53ff3b661328ee7566a3c65f1ccfeef577d9a0b196dc44317dd0823741178e313ea1b76c80963c268fd5b10da248bc076aa7b55fb357768c703cc90825945dcc975f5cd0e842bf2cf37989223cc5e4ceba9f808d1634eea2dd50fc2ec06fe2ac269207d997a9e1a801d92cf7a88c8e85d1308b6ac9e323e8472eef4ba97aa5e63765be2959952ef8e3dba49da5a283147506a181e4084f7e6e9a7c0b26c5683a013ad2ad0b4bb6722eeb6413ab9abc66052fd3ec1ee45d9eb10fe627b72dedf546a8fd7fb4184a15487ce4199a39edb1a1439bd8b06d737a61fe5fa8611f1bd141f869ec695c1295f0f0618435dacffdefb00887708c093720b9ca7b77db43e8a102109ed1ed5ba41ab2e516503dc73f2034a1885315176f80514388feccfcc8c47b9a7612e18e9059f022540b39d8a70fcbc1501765753d990d88231caceb6aec666bff35f58c6581894340eb07b5abe08b3648d70f012c2a034d1d15a54d23b0df398bc860f6f973c445f4715555d3f0c96faaa9db25fda46b62bb1af24e7fd619bf70304e53ba3c2ec2e67ba8509cda1e5e6cbd48a5fe41fbdc96e931bcb2a22ec6a492252e118d8234bc01db16080093ad2c29b8bd5c51c0d2e8801d020b250ca0984291e2707097cd8d71d7bce74a200405df9a32755c95e85983c1888dbc0af509ad3f75012c5b6b621bb324e5f6b93e15d0cb90fd99e27a1c2b38597bc339ca01bf3edf5a565e3ccad29c4f56509ac7c2ceb15da8d85f58cfb694bd0cc33df5bd743c0d074178935b525b55671dccf638b853440856af474dc192981610619319b47e1d8c1a3f8cdcf25d9ce5696b1fbb5b88648a7bb9eb4786de69081252d7f0a2b8b4c668c455a9fe2c5d58e29aee6b21e5cdf3cafa3a23e90bc350eb33d25583ea2b6430bf3f9f674afbef2292482fd36701011885354d36fb32829bc518faf593d035bb1cb7ffbf173d85e4038b97be2b3a9ed3b5610ee8e520acb0e49e6b190cab916307f35a837fe366db78cd8c47a53560a6d2334dbbe39245fb2e354564bb822797f6a4b81d0a0cd3aa46de61f105aed59c013d7c6ea3557638f7b2b2e5a49aacc584f954820cd864b60872d61203e47f821a5a7c2434bdf76d8df505fd43ee817bbdc26868a78780f38e5e8c4c8d4bdc05e8409275e05d7723de6d69fd3a46e561232c28871be3b0b4f9c650819aed18c31dd6a9b3b49d95db5f948bfe36a067a3e89a13d7275e778f23ca2b0d22d7198be3b2246286922c4376ac15054c2fbd5b1e5ad06ad53a0209675298172d7a0c55e1ed253398e724a17259a5a029b1adb035857fceee4765836838628926a16e3e996f13fa49336e50f91579d702e85713549cc58047c898b94a7c416dab6530d93a1022580dfb8ed9958dc7f9c7461163537baf70a8dae718ae00a57360eb1c89e597d31d44dced0d8c62d2ac4cb984b331f3316a200881029c359d51231584b4dfe2455ded5e929ce62fd56d3e5128155e68e995ad0b0c9321397c4419ed2d0f47654e9aa8720454d36e7ec84996150f6fa5d37eeb88eb1faecb7dd50008653d69a186e76610d677548e6c42d76bbeb24841cd92bb75364ebeeed0539d2cc85168b205b7bcfcbe6de3f30ad5438db14b2b50ca2efe4fbd5a4734c235f89ee0cf51b1e58f7a1d61e2c1059b8cc5554359f555e64a0a8e7f416c2648c7eb9440d3454f2108bcafcb4c3f553e6a12592ea73d8e495830837e8f1dbf3c64fccae976e32ece6f87593cc63e3dfdf12a33d060be2b5dd2e620dfd9f9394b435580844310237e4945d028dba6321e39b2d2139a1dd2e09531a83b52b2482fcf125d5b678560861aa926f3a77b78e5a2fc990e0b1267c4098af80e64f08fb1be05725c23120f5fbd28ace858d26491823e9b0bb3b278dfe87bfa98fc9ffefdb76f93c78a3bc5b21f0097f3e0452f4f2c82c06cc703798e455889e243ab0d54d30a4520ea4ca888fa3c1a6c2e87de9cebb2847ddfd6541b7a5d48c02c8760965ab83f28c318d0ce4cdce192fee60e88ecee2e55166d9e8406cb9a328a103bb592c65a0011806f82ed6f27d46505306e5f2cfa4b8e52b70aac2735043d25e955d8616373f3d7d235690bcfdc3633702b6341c3020ec5659ae619244f4093dc9d7e32afa3a44bb0697a63c9e5075e5b871ab4d094e5de8d8e6a05f8744103d24590507ad659762af2c4a071b5e4f7d4be9486cdbc5d6c7e08109216bab4c2d4eb3e39a1f9f3d83f7c6a6e4dc72a323cf08742cad4dc47d6427c970289bec34d87c17b54f0cdf0119a6a4ac9ec45c5b0d99de1f46275f340463d89afd49bec6e7cb97a0a8c691aa081af6376d87d478f8a12526bfc5b3328827430cc241e8dc3f05086bf1628a04164637c812c778a68ce95ff2a17cdf6cbda53937ad47c66a7f30477e25cfd1309eea6d92c564198a11b1b825ae4d43108e39e8c7ced2e7fe39c8536241af029a6eebb7297818e1319185b21ca6d0d514f675684920b2cd8db642bc456063d014838b5d78fd5d669b8ad0ffd65e037f72b2a6b99f1285e5c36c3b47d01f7de83d3af4cb035258a1f8a95581178f7b02843e6c3f06d4cd9755c9fd3841cc8822ce528504db2282f1688bd3d12b8dbc779a23df10d45cbf8739a1103e425dab334883821728de605925bb751ba9e84929a67e57458a6a42657feaec85a2733ce4fbb1cb6fb12e834c6a560bd5d489f4cc87510e879ce68739ce0495aba39e4d3c0d05e5b3970b2b0e70de2f2ee183b1cefb090154c62e27edbdc3b0772cfc8c7ae49e1fd698e1ceaac23e535fda5789509364572c3ac2dad13e4eae68dabbc7fd15d07a15935a84ae432e9d01b34b8f125e711400da2ba29ee02e5aba0599eb424bba49ff71eafabe2b4704c9f71aaa63d6c9c943bda2ca97e36872a131114c01de44ecf036ce3699f258bd7c79b39442ae77032fa60fc307d3fafaace86b9d15008c7e3af31d8b03f95a8e5cb52dee4708382468b10d7bc88889b423e6ce7e09f6eb852006acf58f83c627afc765705397ef28450e194edae9cb28b3161ef2558a690dd219ce492592401d4d70f6bd8f829f24de3eb16e5604c78449fb0ac0d849e600b0da04ad95e87f646d2e07d1f3efbe90b6a5cdce72b69f086940237c1b16e1f9c46d44cb3ac66599fcebb5673c57ec2311b4b8028b2ddc64c4893d80121bbc3cda9ab06fb13b4f9d44b4004a22d425940d7042ddd8343d711dd6c490ac094cfafa362bef0a6e3aaa857bde68693136599dde689ae01a87381f10fecdbd6fda5d5cdf60eda5cd189676a5c5ef2ff26a517c4171c908a6cf068efbaeb9bd6d5c5d336a442eb65874dcb5223ca19f3b06c70d0d349411e50ccaf38de83deb23930332504769a4f943cea8a345a4b740c83255b01ad089e647f58008f0cbfb467a9c26bd28e956cbae9eff36da3a18dcad1756df51b2cd468866928dd5cf84b069982329b242360ad53a61afed48e335c64bd7352bf7c5fbbe708ee7477e2b2430820b46e9ef2d603180640607c38813154eaf66b4c845325b00843ebe587e1197f0ac3501ca1437cdaff08ad9ca59b04d88055f4834f97be2d611c14b1e7c397a23345c5897a3fcdf606f849b5dd4f91e971fda828d59cab867ff1ad20ac259059c9439af8d4fca64f1aa989f993baaf8e6fc2ea402e4438b05067f9a9ecd62963d8a190ba4b6762d5762ace0fd32259a30289ea21ed138686d321439d0419767bc024c47e4d56a356837219d750ea85759b0a155409965e60ec371bd0ec5d4335d044431e830b2e5d38a4f976d4c3f93bfe6002a0b012f96e0ea2ff07345b1aa7a9dc5c24c921e2072466ee5ea8398b2b8ed8cf9c5458069da157266845be4b35a31eb15b227b0711a15b4fec2d7280d8173d92a98948bc9af046e22db4c682867f586f38829837609317a3966cf46e1717d1676bea38ee72cf017ceedc56c46365314b8bcbcbad655901e33bee059061e9e6e479db0244ebf1e4adef4f6b18d46c3b7668bca44887c1032225b12b5ddb1fadb4ed120b1c4a098995ea6b5df1d701f1b11d4d04511968e5b19f468dff97c220171bb064bc7a58f139a221f5c8cb5be4b006aa565c53fb4a823617926b080301bfca773355c930eb1cdbf205390baca064d57f96362a7180009d26fefc2184fc059a2c97fef738c008caa8bb9eb12da9c2f6c8426e74795eb66ba7ff61e1bff5dee2e8d882ea1d93031d1e6ae0b91fa87e4186af69b4e1e891b137f4510ae4058b7fc819d3736c93937381d06fad0c053ffd0597a3a2c7a62188f4534f3f17c8ee4c45fdce53e9f3e63853ab26a1ab75b0c2d146e95663baa4384f9e059c4393a04eee0163ba4ff7f2c466bbd1c91b98a36c431a664f7c695c60b45810ad8ebf7bc320520d926a5dc1f594f5ca91d2d46bb0fc8d3ffec7eaf055369a38531f21d5d728567d21aa716297c5614b72e7080d20145389f74cd8613a03e14c198879756b3373c4dc6dd459ef9965ca3c43d7a2a8035e364d5b878115bc6a01f1dfcf1a5d4fa5a48e8bebef1eada2bb7df06be0c06f6a6c3462d1c75795eb6886a60f391653a5f1215e024d8cc9c54d0c7f3c0a60a0c19e0f42721e2c54ca41c7955adc84485276ea21d0577eaed30d3610421bb43b4b37853be1806c567c1b91ee1100bddf9823929cc58bee26044fef850cb4b18664caa19b326418f0f3f65b463ed945aa9e261ea7c21ccce261a51b64d451ea08bb83f87aac01acf95f48e5234875313ccf9ebf91d4aaa469ec18010e1f58349bb7f0caf8176ff26f964a26017b7c68f483e4afc48ab0bcf2042f4e157d2637ea6a82305b3bfc874603983360478c526ec767359693ea09d713c502b05f0a57fbd98f7e62b3efbfdfea0951d6cf86b12ad784c898ce11353fc43e56b074274c5971e62a9edb715d06c8298d195cfbaf063c4ec2e0a21539e0b7691d5355fb660566e07578d6d3b9224c7aecdd2025c5b0e7d26bc25cb510d080d52a6d829b8b1462a438bb707504a0697f6adde8260764496ac5a92a77d37036d37de03745f2c820871848059d52e94d964ba17f450b82a6c3322da517c42e2b51995b1086f8953aada1681eb43f366eb324b52b11a89376e2acd14ab150676592b585951b6c15cdb39b8ca46a679524cfa5adb3c954ca0bb69e40462dd40a0fe4aa3fc7f870435ec62455ec8949c40414404ad85cf457fa31b0a9d7b8b103dd928e3437e6d77279f3763b77e82d10cd8992b43ca6da87170edd841528061af25281f10e4093acbbe5c178f98c2001703d14ff8a0fec41281f5203aaad5c19367e234e9063872ea13ca044c14cf04cf7480dc319ce13077fdd8814248fb27482709d0ec22d15b6255f8c741d0c4bd8b96369da640891560a3e91f185e81b02b69ab21a72646462e3050e057e99b0c5e397aec9274ff3080943e0fbe7471f58c7a26cdf6933912762cbea684abdac71f4ce03794c3413feeed7938d4824ed2aedeffbe95c2937084415358595269b25f4f754667fcddbd237b1d1a1ea5790986ce2519cd5f128251369a935da513d7ed49a0662b049064f92b9c04b9d139d4f494b9d09158263971d43972c9641dda391b377690f2e4053259a59763989573ad5ac153a843c5494abb888e08645701dbc1488fba2f9b2ddfe9a94a2c75f85a342be41523017887d548210bffb884fa759431ab50bc6a4bf08248ad656de327a34c0555b1ac633db702057d28e6f7c8abddbdfa71d9122b9ffb135062b528bb973093a0b20041e89cb289d744e1f57e8d66ef2ae145250d95fe8fbf6ef9379ccdd0012b9bcb52fee5c0c0ac148b2824c5f71af959d4da4d8a33e25a3dca66af7bd0f59f0d2359fc"
|
|
</script>
|
|
</html>
|