miitomo tomodachi miitopia qr decrypt experiment
by arian_
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/sjcl.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/core/codecBytes.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/qr.min.js"></script>
<script src="https://cdn.jsdelivr.net/gh/criteo-forks/qr-scanner/qr-scanner.umd.min.js"></script>
</head>
<body>
<!-- rudimentary js error handling text -->
<div id="error-container" style="display: none;">
<h1>😠JavaScript Error!! ðŸ˜</h1>
<p id="error-message" style="color: red;"></p>
<pre id="error-stacktrace" style="display: none;"></pre>
<pre id="error-at">At <span></span></pre>
</div>
<!-- Tabbing Section
<div>
<button class="tab-button active" onclick="showTab('encode-tab')">Encode</button>
<button class="tab-button" onclick="showTab('decode-tab')">Decode</button>
</div>
-->
<!-- Encode Section -->
<div id="encode-tab" class="tab-content active">
<h1>encrypt/encode mii qr code</h1>
<div>
<b>StoreData hex input:</b>
<div>(you can also paste Base64 into this, or, load from a cfsd file below)</div>
</div>
<table id="hex-editor-storedata" border>
<tr>
<td></td>
<td><pre class="table-padding"></pre></td>
</tr>
<tr>
<td class="line-numbers" width="80"></td>
<td>
<textarea spellcheck="false" class="hex-textarea" cols="48"></textarea>
</td>
<td width="160" class="ascii-output">.</td>
</tr>
</table>
<input type="file" id="load-storedata-btn" accept=".cfsd,.ffsd,.3dsmii,.cfcd,.ffcd,.bin,.dat" onchange="loadStoreDataFromFile(event)">
<details>
<summary>load extra data for tomodachi life/miitomo/miitopia 3ds:</summary>
<table id="hex-editor-extra" border>
<tr>
<td></td>
<td><pre class="table-padding"></pre></td>
</tr>
<tr>
<td...
CSS
body {
font-family: sans-serif;
}
.tab-content {
display: none;
}
.active {
display: block;
}
.tab-button {
padding: 10px;
cursor: pointer;
display: inline-block;
border: 1px solid #ddd;
background-color: #f0f0f0;
margin-right: 5px;
}
.tab-button.active {
background-color: #ddd;
}
table, table * {
margin: 0;
padding: 0;
vertical-align: top;
font: 1em/1em monospace;
}
.hex-textarea {
height: 1.5em;
resize: none;
width: 100%;
}
.table-padding {
padding: 0 2px;
}
.file-input {
position: absolute;
opacity: .001;
}
.ascii-output {
overflow: hidden;
}
#extra-data-warning {
color: red;
}
JavaScript
// AES keys.
const AES_CCM_KEYSLOT_0x31_HEX = '59FC817E6446EA6190347B20E9BDCE52'; // Type 2, slot 0x31
// https://www.3dbrew.org/wiki/PSPXI:EncryptDecryptAes#Key_Types
const AES_CCM_KEYSLOT_0x31_DEV_HEX = '12DF92B6FFD438AB291C4FD4D7CE256D'; // Dev variant of above key.
const AES_CTR_KEY_HEX = '30819F300D06092A864886F70D010101';
// Converted AES-CCM keys for sjcl
const AES_CCM_KEYSLOT_0x31_BITS = sjcl.codec.hex.toBits(AES_CCM_KEYSLOT_0x31_HEX);
const AES_CCM_KEYSLOT_0x31_DEV_BITS = sjcl.codec.hex.toBits(AES_CCM_KEYSLOT_0x31_DEV_HEX);
let gAESCCMKeyPrimary = AES_CCM_KEYSLOT_0x31_BITS; // Reassigned to dev/prod.
const toggleAESCCMKeyMode = isDev => // true controls whether is dev or not
{ gAESCCMKeyPrimary = isDev ? AES_CCM_KEYSLOT_0x31_DEV_BITS : AES_CCM_KEYSLOT_0x31_BITS };
const crpyto = window['crypt'+'o']; // jsfiddle blocks this word???
const sc = crpyto.subtle; // Shortcut to SubtleCr*pto.
// ^^ Needed or else the fiddle will not save
// Utility: Hex <-> Uint8Array conversion
function hexToUint8Array(hex) {
const bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hex.substr(i * 2, 2), 16);
}
return bytes;
}
function uint8ArrayToHex(bytes) {
return Array.from(bytes).map(b => b.toString(16).padStart(2, '0')).join('');
}
// Utility: Base64 <-> Uint8Array conversion
function base64ToUint8Array(base64) {
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
// Helper function to check if a string is valid hex
function isHex(str) {
const hexRegex = /^[0-9A-Fa-f]+$/;
return hexRegex.test(str);
}
// Helper function to check if a string is valid base64
function isBase64(str) {
try {
atob(str); // `atob` will throw an error if the string is not valid base64
...