HTML preprocessors can make writing HTML more powerful or convenient. For instance, Markdown is designed to be easier to write and read for text documents and you could write a loop in Pug.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. So you don't have access to higher-up elements like the <html>
tag. If you want to add classes there that can affect the whole document, this is the place to do it.
In CodePen, whatever you write in the HTML editor is what goes within the <body>
tags in a basic HTML5 template. If you need things in the <head>
of the document, put that code here.
The resource you are linking to is using the 'http' protocol, which may not work when the browser is using https.
CSS preprocessors help make authoring CSS easier. All of them offer things like variables and mixins to provide convenient abstractions.
It's a common practice to apply CSS to a page that styles elements such that they are consistent across all browsers. We offer two of the most popular choices: normalize.css and a reset. Or, choose Neither and nothing will be applied.
To get the best cross-browser support, it is a common practice to apply vendor prefixes to CSS properties and values that require them to work. For instance -webkit-
or -moz-
.
We offer two popular choices: Autoprefixer (which processes your CSS server-side) and -prefix-free (which applies prefixes via a script, client-side).
Any URLs added here will be added as <link>
s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.
You can apply CSS to your Pen from any stylesheet on the web. Just put a URL to it here and we'll apply it, in the order you have them, before the CSS in the Pen itself.
You can also link to another Pen here (use the .css
URL Extension) and we'll pull the CSS from that Pen and include it. If it's using a matching preprocessor, use the appropriate URL Extension and we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Any URL's added here will be added as <script>
s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.
You can apply a script from anywhere on the web to your Pen. Just put a URL to it here and we'll add it, in the order you have them, before the JavaScript in the Pen itself.
If the script you link to has the file extension of a preprocessor, we'll attempt to process it before applying.
You can also link to another Pen here, and we'll pull the JavaScript from that Pen and include it. If it's using a matching preprocessor, we'll combine the code before preprocessing, so you can use the linked Pen as a true dependency.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Titan Cryptographer</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.2.0/crypto-js.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/forge/1.3.1/forge.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tweetnacl/1.0.3/nacl.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/eccrypto-js@5.6.0/dist/eccrypto-js.js"></script>
</head>
<body class="min-h-screen flex flex-col">
<div class="matrix-bg">
<canvas id="matrixCanvas"></canvas>
</div>
<div id="loading-overlay">
<div class="spinner"></div>
<p id="loading-text" class="loader-text" data-translate="processing">Processing...</p>
</div>
<header class="py-4 px-6 shadow-lg glass-panel m-2 md:m-4 rounded-lg">
<div class="container mx-auto flex flex-wrap justify-between items-center gap-y-2">
<h1 class="text-3xl md:text-4xl titan-header" data-translate="appTitle">Titan Cryptographer</h1>
<div class="flex items-center space-x-3">
<button id="howToUseBtn" class="titan-button-secondary text-sm px-3 py-2" data-translate="howToUseButton" style="width: 160px;">How to Use</button>
<select id="language-switcher" class="titan-select text-sm !p-2">
<option value="en">English (US)</option>
<option value="ja">日本語</option>
<option value="zh-TW">繁體中文</option>
</select>
</div>
</div>
</header>
<main class="container mx-auto p-2 md:p-4 flex-grow w-full max-w-screen-2xl">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 md:gap-6">
<div class="glass-panel p-4 md:p-6 space-y-6">
<div>
<label for="encryption-method" class="block text-sm font-medium mb-2" data-translate="selectAlgorithm">Select Encryption Method:</label>
<select id="encryption-method" class="titan-select w-full">
<option value="aes" data-translate="algoAES">AES-256</option>
<option value="rsa" data-translate="algoRSA">RSA</option>
<option value="hill" data-translate="algoHill">Hill Cipher</option>
<option value="xsalsa20" data-translate="algoXSalsa20">XSalsa20-Poly1305</option>
<option value="ecies" data-translate="algoECIES">ECIES</option>
</select>
</div>
<div>
<label for="input-text" class="block text-sm font-medium mb-2" data-translate="inputTextLabel">Input Text:</label>
<textarea id="input-text" rows="6" class="titan-textarea w-full" data-translate-placeholder="inputTextPlaceholder" placeholder="Enter text to encrypt/decrypt or drop a file..."></textarea>
<div class="mt-3 flex items-center gap-3">
<label for="file-input" class="titan-button-secondary text-sm px-3 py-2 cursor-pointer inline-flex items-center">
<i class="ti ti-upload"></i><span data-translate="uploadFileButton">Upload Text File</span>
</label>
<input type="file" id="file-input" class="hidden" accept=".txt,.json,.md,.csv">
<span id="file-name" class="text-sm text-gray-400 truncate max-w-xs"></span>
</div>
</div>
<div id="algo-specific-controls" class="space-y-5">
<div id="aes-controls" class="algo-controls-group space-y-2">
<label for="aes-key" class="block text-sm font-medium" data-translate="aesKeyLabel">Password/Key:</label>
<div class="relative">
<input type="password" id="aes-key" class="titan-input w-full pr-10" data-translate-placeholder="aesKeyPlaceholder" placeholder="Enter AES password">
<button type="button" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-cyan-400 focus:outline-none" onclick="App.togglePasswordVisibility('aes-key')">
<i class="ti ti-eye"></i>
</button>
</div>
</div>
<div id="rsa-controls" class="algo-controls-group hidden space-y-3">
<div>
<label class="block text-sm font-medium mb-1" data-translate="rsaKeySizeLabel">Key Size (bits):</label>
<select id="rsa-key-size" class="titan-select w-full">
<option value="2048">2048</option>
<option value="4096">4096</option>
</select>
<button id="rsa-generate-keys" class="titan-button-secondary w-full mt-2.5" data-translate="rsaGeneratePairButton">Generate Key Pair</button>
</div>
<div>
<label for="rsa-public-key" class="block text-sm font-medium" data-translate="rsaPublicKeyLabel">Public Key (PEM):</label>
<textarea id="rsa-public-key" rows="3" class="titan-textarea w-full" data-translate-placeholder="rsaPublicKeyPlaceholder" placeholder="Enter or generate public key"></textarea>
</div>
<div>
<label for="rsa-private-key" class="block text-sm font-medium" data-translate="rsaPrivateKeyLabel">Private Key (PEM):</label>
<textarea id="rsa-private-key" rows="3" class="titan-textarea w-full" data-translate-placeholder="rsaPrivateKeyPlaceholder" placeholder="Enter or generate private key"></textarea>
</div>
</div>
<div id="hill-controls" class="algo-controls-group hidden space-y-3">
<div>
<label class="block text-sm font-medium mb-1" data-translate="hillMatrixSizeLabel">Matrix Size:</label>
<select id="hill-matrix-size" class="titan-select w-full">
<option value="2">2x2</option>
<option value="3">3x3</option>
</select>
</div>
<div>
<label class="block text-sm font-medium" data-translate="hillKeyMatrixLabel">Key Matrix (numbers 0-25):</label>
<div id="hill-matrix-input" class="hill-matrix-grid grid-cols-2">
</div>
</div>
</div>
<div id="xsalsa20-controls" class="algo-controls-group hidden space-y-2">
<label for="xsalsa20-key" class="block text-sm font-medium" data-translate="xsalsa20KeyLabel">Key (32 bytes, Base64):</label>
<div class="relative">
<input type="password" id="xsalsa20-key" class="titan-input w-full pr-10" data-translate-placeholder="xsalsa20KeyPlaceholder" placeholder="Enter Base64 key or leave blank to generate">
<button type="button" class="absolute inset-y-0 right-0 px-3 flex items-center text-gray-400 hover:text-cyan-400 focus:outline-none" onclick="App.togglePasswordVisibility('xsalsa20-key')">
<i class="ti ti-eye"></i>
</button>
</div>
<button id="xsalsa20-generate-key" class="titan-button-secondary w-full mt-1.5" data-translate="xsalsa20GenerateKeyButton">Generate Key</button>
<p class="text-xs text-gray-400 pt-1" data-translate="xsalsa20NonceInfo">Nonce is handled automatically.</p>
</div>
<div id="ecies-controls" class="algo-controls-group hidden space-y-3">
<div>
<label class="block text-sm font-medium mb-1" data-translate="eciesCurveLabel">Elliptic Curve:</label>
<select id="ecies-curve" class="titan-select w-full">
<option value="secp256k1">secp256k1</option>
</select>
<button id="ecies-generate-keys" class="titan-button-secondary w-full mt-2.5" data-translate="eciesGeneratePairButton">Generate Key Pair</button>
</div>
<div>
<label for="ecies-public-key" class="block text-sm font-medium" data-translate="eciesPublicKeyLabel">Public Key (Hex or Base64):</label>
<textarea id="ecies-public-key" rows="2" class="titan-textarea w-full" data-translate-placeholder="eciesPublicKeyPlaceholder" placeholder="Enter or generate public key"></textarea>
</div>
<div>
<label for="ecies-private-key" class="block text-sm font-medium" data-translate="eciesPrivateKeyLabel">Private Key (Hex or Base64):</label>
<textarea id="ecies-private-key" rows="2" class="titan-textarea w-full" data-translate-placeholder="eciesPrivateKeyPlaceholder" placeholder="Enter or generate private key"></textarea>
</div>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3 pt-2">
<button id="encrypt-btn" class="titan-button">
<i class="ti ti-lock"></i><span data-translate="encryptButton">Encrypt</span>
</button>
<button id="decrypt-btn" class="titan-button">
<i class="ti ti-key"></i><span data-translate="decryptButton">Decrypt</span>
</button>
<button id="clear-btn" class="titan-button-secondary">
<i class="ti ti-refresh"></i><span data-translate="clearButton">Clear All</span>
</button>
</div>
</div>
<div class="glass-panel p-4 md:p-6 space-y-6 flex flex-col">
<div>
<label for="output-text" class="block text-sm font-medium mb-2" data-translate="outputTextLabel">Output Text:</label>
<div class="relative">
<textarea id="output-text" rows="6" class="titan-textarea w-full" readonly data-translate-placeholder="outputTextPlaceholder" placeholder="Encrypted/decrypted text will appear here..."></textarea>
<button id="copy-output-btn" class="absolute top-2.5 right-2.5 titan-button-secondary !p-2 rounded-md" title="Copy to Clipboard">
<i class="ti ti-copy !mr-0"></i>
</button>
</div>
</div>
<div id="error-message-area" class="hidden p-3 bg-red-700/30 border border-red-500 rounded-md text-red-300 text-sm">
<p id="error-message-text"></p>
</div>
<div class="flex-grow flex flex-col min-h-0">
<div class="flex border-b border-cyan-500/30">
<button class="tab-button active" onclick="App.openTab(event, 'learn-more-tab', this)" data-translate="learnMoreTab">Learn More</button>
<button class="tab-button" onclick="App.openTab(event, 'how-to-use-tab', this)" data-translate="howToUseTab">How to Use</button>
</div>
<div id="learn-more-tab" class="tab-content active flex-grow overflow-y-auto">
<h3 id="learn-more-title" class="text-xl titan-header mb-2 sticky top-0 bg-opacity-80 backdrop-blur-sm py-2" style="background-color: rgba(20, 28, 46, 0.8);">AES-256</h3>
<p id="learn-more-description" class="text-sm text-gray-300 leading-relaxed">
</p>
<div id="learn-more-details" class="mt-3 text-xs text-gray-400 space-y-1">
</div>
</div>
<div id="how-to-use-tab" class="tab-content flex-grow overflow-y-auto">
<h3 class="text-xl titan-header mb-2 sticky top-0 bg-opacity-80 backdrop-blur-sm py-2" style="background-color: rgba(20, 28, 46, 0.8);" data-translate="howToUseGuideTitle">How to Use Titan Cryptographer</h3>
<div id="how-to-use-content" class="text-sm text-gray-300 leading-relaxed space-y-3">
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="py-6 px-6 mt-10 text-center text-sm text-gray-400">
<p>
<span data-translate="madeBy">Made by Ed Chen</span>
(<a href="https://www.linkedin.com/in/ed-chen-saas/" target="_blank" rel="noopener noreferrer" class="text-cyan-400 hover:text-cyan-300 inline-flex items-center group">
<i class="ti ti-brand-linkedin mr-1 group-hover:text-cyan-200 transition-colors"></i> <span class="group-hover:underline">LinkedIn</span>
</a>)
</p>
</footer>
<div id="genericModal" class="modal">
<div class="modal-content glass-panel">
<button class="modal-close-btn" onclick="App.closeModal('genericModal')"><i class="ti ti-x"></i></button>
<h2 id="genericModalTitle" class="modal-title">Modal Title</h2>
<div id="genericModalBody" class="modal-body">
<p>Modal content goes here.</p>
</div>
</div>
</div>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Orbitron:wght@400;500;700&display=swap');
body {
font-family: 'Inter', sans-serif;
background-color: #0A0F1A; /* Deep space blue/black */
color: #E0E0E0; /* Light grey for text */
overflow-x: hidden; /* Prevent horizontal scroll */
}
.titan-header {
font-family: 'Orbitron', sans-serif; /* Futuristic font for titles */
color: #7DF9FF; /* Electric cyan accent */
}
/* Custom scrollbar for a more modern look */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #1E293B; /* Darker track */
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: #4A5568; /* Muted thumb color */
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #718096; /* Lighter on hover */
}
/* Glassmorphism effect for panels/modals */
.glass-panel {
background: rgba(20, 28, 46, 0.65); /* Semi-transparent dark blue, slightly more opaque */
backdrop-filter: blur(12px) saturate(180%); /* Increased blur and saturation */
-webkit-backdrop-filter: blur(12px) saturate(180%);
border: 1px solid rgba(125, 249, 255, 0.25); /* Faint cyan border, slightly more visible */
border-radius: 1rem; /* Rounded corners */
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.4); /* Slightly stronger shadow */
}
/* Input fields styling */
.titan-input, .titan-textarea, .titan-select {
background-color: rgba(30, 41, 59, 0.85); /* Darker input background */
border: 1px solid rgba(125, 249, 255, 0.35);
color: #E0E0E0;
border-radius: 0.5rem;
padding: 0.85rem 1.1rem; /* Slightly increased padding */
transition: border-color 0.3s ease, box-shadow 0.3s ease;
width: 100%; /* Ensure full width */
}
.titan-input:focus, .titan-textarea:focus, .titan-select:focus {
outline: none;
border-color: #7DF9FF;
box-shadow: 0 0 0 3px rgba(125, 249, 255, 0.4); /* Enhanced focus ring */
}
.titan-textarea {
min-height: 120px; /* Ensure textarea has a decent default height */
}
/* Button styling */
.titan-button {
background-image: linear-gradient(to right, #3B82F6, #8B5CF6); /* Blue to purple gradient */
color: white;
font-weight: 600;
padding: 0.85rem 1.6rem; /* Slightly increased padding */
border-radius: 0.5rem;
transition: all 0.3s ease;
border: none;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
display: inline-flex; /* For icon alignment */
align-items: center;
justify-content: center;
}
.titan-button:hover {
transform: translateY(-2px) scale(1.02); /* Added scale effect */
box-shadow: 0 6px 20px rgba(139, 92, 246, 0.5); /* Enhanced shadow */
}
.titan-button-secondary {
background-color: rgba(55, 65, 81, 0.75); /* Slightly more opaque */
border: 1px solid rgba(125, 249, 255, 0.45);
color: #E0E0E0;
padding: 0.85rem 1.6rem; /* Matched padding */
}
.titan-button-secondary:hover {
background-color: rgba(75, 85, 99, 0.95);
box-shadow: 0 4px 15px rgba(125, 249, 255, 0.25);
transform: translateY(-2px); /* Added hover effect */
}
/* Matrix-like background effect (subtle) */
.matrix-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
overflow: hidden;
}
.matrix-bg canvas {
display: block;
}
/* Loading Animation */
#loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(10, 15, 26, 0.95); /* Very dark overlay */
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
opacity: 0;
visibility: hidden;
transition: opacity 0.5s ease, visibility 0.5s ease;
}
#loading-overlay.active {
opacity: 1;
visibility: visible;
}
.loader-text {
font-family: 'Orbitron', sans-serif;
font-size: 1.5rem;
color: #7DF9FF;
margin-top: 20px;
letter-spacing: 2px;
}
.spinner {
border: 6px solid rgba(125, 249, 255, 0.3);
border-top: 6px solid #7DF9FF;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Hill Cipher Matrix Input */
.hill-matrix-grid {
display: grid;
gap: 0.5rem;
margin-top: 0.5rem;
}
.hill-matrix-grid input {
width: 4rem; /* Keep this or adjust based on number of columns */
text-align: center;
}
/* Modal Styling */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(10, 15, 26, 0.85); /* Slightly more opaque */
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
padding: 1rem; /* Padding for smaller screens */
}
.modal.active {
opacity: 1;
visibility: visible;
}
.modal-content {
max-height: 90vh; /* Increased max height */
overflow-y: auto;
padding: 2rem;
width: 100%; /* Full width on small screens */
max-width: 800px; /* Increased Max width for modals */
}
.modal-title {
font-family: 'Orbitron', sans-serif;
font-size: 1.75rem;
color: #7DF9FF;
margin-bottom: 1.5rem; /* Increased margin */
border-bottom: 1px solid rgba(125, 249, 255, 0.3);
padding-bottom: 1rem; /* Increased padding */
}
.modal-body p, .modal-body ul {
margin-bottom: 1rem;
line-height: 1.7;
}
.modal-body code {
background-color: rgba(30, 41, 59, 0.9);
padding: 0.2rem 0.4rem;
border-radius: 0.25rem;
font-family: monospace;
color: #A5F3FC; /* Light cyan for code */
}
.modal-body strong {
color: #A5F3FC;
}
.modal-body h4 {
font-family: 'Orbitron', sans-serif;
color: #A5F3FC;
font-size: 1.1rem;
margin-top: 1.5rem; /* Increased margin */
margin-bottom: 0.75rem; /* Increased margin */
}
.modal-close-btn {
position: absolute;
top: 1.5rem; /* Adjusted position */
right: 1.5rem; /* Adjusted position */
background: none;
border: none;
color: #E0E0E0;
font-size: 1.75rem; /* Slightly larger */
cursor: pointer;
transition: color 0.2s ease;
}
.modal-close-btn:hover {
color: #7DF9FF;
}
/* Tab specific styles */
.tab-button {
padding: 0.75rem 1.25rem; /* Increased padding */
border-radius: 0.5rem 0.5rem 0 0;
margin-right: 0.25rem;
cursor: pointer;
background-color: rgba(30, 41, 59, 0.7);
border: 1px solid rgba(125, 249, 255, 0.2);
border-bottom: none;
color: #A5F3FC;
transition: background-color 0.2s ease, color 0.2s ease;
}
.tab-button.active {
background-color: rgba(40, 52, 75, 0.95); /* Slightly lighter for active tab */
color: #7DF9FF;
font-weight: 600;
border-bottom: 1px solid rgba(40, 52, 75, 0.95); /* Match background for seamless look */
}
.tab-button:not(.active):hover {
background-color: rgba(35, 46, 65, 0.8);
}
.tab-content {
display: none;
padding: 1.5rem; /* Add padding to tab content area */
border: 1px solid rgba(125, 249, 255, 0.2);
/* border-top: none; */ /* Removed to avoid double border with active tab */
border-radius: 0 0.5rem 0.5rem 0.5rem; /* Adjust radius for top-left if first tab is active */
background-color: rgba(20, 28, 46, 0.55); /* Match panel bg */
}
.tab-content.active {
display: block;
}
/* Ensure Tabler icons are sized and aligned */
.ti {
vertical-align: middle;
width: 1.25em; /* Ensure consistent icon sizing */
height: 1.25em;
}
.titan-button .ti, .titan-button-secondary .ti {
margin-right: 0.5rem; /* Space between icon and text in buttons */
}
label .ti { /* For upload button icon */
margin-right: 0.5rem;
}
const App = {
// --- Configuration & State ---
config: {
currentLanguage: 'en',
currentAlgorithm: 'aes',
rsaKeySize: 2048,
hillMatrixSize: 2,
eciesCurve: 'secp256k1',
},
elements: {
// Main elements
languageSwitcher: null,
encryptionMethodSelect: null,
inputText: null,
fileInput: null,
fileNameDisplay: null,
outputText: null,
encryptBtn: null,
decryptBtn: null,
clearBtn: null,
copyOutputBtn: null,
// Algorithm specific controls containers
algoSpecificControlsContainer: null,
aesControls: null,
rsaControls: null,
hillControls: null,
xsalsa20Controls: null,
eciesControls: null,
// AES
aesKeyInput: null,
// RSA
rsaKeySizeSelect: null,
rsaGenerateKeysBtn: null,
rsaPublicKeyInput: null,
rsaPrivateKeyInput: null,
// Hill Cipher
hillMatrixSizeSelect: null,
hillMatrixInputContainer: null,
// XSalsa20
xsalsa20KeyInput: null,
xsalsa20GenerateKeyBtn: null,
// ECIES
eciesCurveSelect: null,
eciesGenerateKeysBtn: null,
eciesPublicKeyInput: null,
eciesPrivateKeyInput: null,
// Error display
errorMessageArea: null,
errorMessageText: null,
// Loading overlay
loadingOverlay: null,
loadingText: null,
// Educational content
learnMoreTitle: null,
learnMoreDescription: null,
learnMoreDetails: null,
howToUseContent: null,
howToUseBtn: null,
// Modals
genericModal: null,
genericModalTitle: null,
genericModalBody: null,
},
// --- Localization Strings ---
translations: {
en: {
appTitle: "Titan Cryptographer",
selectAlgorithm: "Select Encryption Method:",
algoAES: "AES-256 (Symmetric)",
algoRSA: "RSA (Asymmetric)",
algoHill: "Hill Cipher (Mathematical)",
algoXSalsa20: "XSalsa20-Poly1305 (Symmetric Stream)",
algoECIES: "ECIES (Elliptic Curve)",
inputTextLabel: "Input Text:",
inputTextPlaceholder: "Enter text to encrypt/decrypt or drop a file...",
uploadFileButton: "Upload Text File (.txt, .json, .md, .csv)",
aesKeyLabel: "Password/Key:",
aesKeyPlaceholder: "Enter AES password (min 8 chars recommended)",
rsaKeySizeLabel: "Key Size (bits):",
rsaGeneratePairButton: "Generate New RSA Key Pair",
rsaPublicKeyLabel: "Public Key (PEM Format):",
rsaPublicKeyPlaceholder: "Paste RSA public key here or generate one",
rsaPrivateKeyLabel: "Private Key (PEM Format):",
rsaPrivateKeyPlaceholder: "Paste RSA private key here or generate one",
hillMatrixSizeLabel: "Matrix Size:",
hillKeyMatrixLabel: "Key Matrix (numbers 0-25, space/comma separated per row):",
hillMatrixPlaceholder: "e.g., 2x2: 3 10 5 7 or 3,10\n5,7",
xsalsa20KeyLabel: "Key (32 bytes, Base64 Encoded):",
xsalsa20KeyPlaceholder: "Enter Base64 key or leave blank to auto-generate",
xsalsa20GenerateKeyButton: "Generate New XSalsa20 Key",
xsalsa20NonceInfo: "Nonce (Number used once) is handled automatically and prepended to ciphertext.",
eciesCurveLabel: "Elliptic Curve:",
eciesGeneratePairButton: "Generate New ECIES Key Pair",
eciesPublicKeyLabel: "Public Key (Hex or Base64):",
eciesPublicKeyPlaceholder: "Paste ECIES public key here or generate one",
eciesPrivateKeyLabel: "Private Key (Hex or Base64):",
eciesPrivateKeyPlaceholder: "Paste ECIES private key here or generate one",
encryptButton: "Encrypt",
decryptButton: "Decrypt",
clearButton: "Clear All",
outputTextLabel: "Output Text:",
outputTextPlaceholder: "Encrypted/decrypted text will appear here...",
copyToClipboard: "Copy to Clipboard",
copied: "Copied!",
processing: "Processing...",
errorTitle: "Error",
madeBy: "Made by Ed Chen",
howToUseButton: "How to Use",
learnMoreTab: "Learn More",
howToUseTab: "How to Use",
howToUseGuideTitle: "How to Use Titan Cryptographer",
// Learn More Content (English)
learnMore_aes_title: "AES-256 (Advanced Encryption Standard)",
learnMore_aes_desc: "AES-256 is a symmetric block cipher widely adopted globally. It uses a 256-bit key for both encryption and decryption, offering strong security. 'Symmetric' means the same key is used for both processes.",
learnMore_aes_details: `<ul>
<li><strong>Type:</strong> Symmetric Block Cipher</li>
<li><strong>Key Size:</strong> 256 bits</li>
<li><strong>Block Size:</strong> 128 bits</li>
<li><strong>Common Uses:</strong> Securing sensitive data, file encryption, secure communications.</li>
<li><strong>Note:</strong> Password you enter is typically hashed (e.g. with SHA-256) to derive the actual 256-bit encryption key. Keep your password secret.</li>
</ul>`,
learnMore_rsa_title: "RSA (Rivest-Shamir-Adleman)",
learnMore_rsa_desc: "RSA is an asymmetric encryption algorithm. It uses a pair of keys: a public key for encryption and a private key for decryption. The public key can be shared openly, while the private key must be kept secret.",
learnMore_rsa_details: `<ul>
<li><strong>Type:</strong> Asymmetric Public-Key Cryptosystem</li>
<li><strong>Key Sizes:</strong> Commonly 2048 or 4096 bits. Larger keys offer more security but are slower.</li>
<li><strong>Process:</strong> Encrypt with Public Key, Decrypt with Private Key. (Also used for digital signatures: Sign with Private, Verify with Public).</li>
<li><strong>Key Format:</strong> Often PEM (Privacy Enhanced Mail) format.</li>
<li><strong>Note:</strong> RSA is slower than symmetric ciphers like AES, so it's often used to encrypt a symmetric key, which then encrypts the bulk data (hybrid encryption).</li>
</ul>`,
learnMore_hill_title: "Hill Cipher",
learnMore_hill_desc: "The Hill Cipher is a polygraphic substitution cipher based on linear algebra. It encrypts blocks of letters (typically 2 or 3) using matrix multiplication. The key is a square matrix.",
learnMore_hill_details: `<ul>
<li><strong>Type:</strong> Polygraphic Substitution Cipher (Symmetric)</li>
<li><strong>Key:</strong> An N x N matrix of integers (modulo 26 for English alphabet A-Z).</li>
<li><strong>Process:</strong> Converts blocks of N letters into vectors, multiplies by the key matrix (mod 26). Decryption uses the inverse of the key matrix (mod 26).</li>
<li><strong>Requirement:</strong> The key matrix must be invertible modulo 26 for decryption to be possible. This means its determinant must be coprime to 26 (i.e., not divisible by 2 or 13).</li>
<li><strong>Security:</strong> Vulnerable to known-plaintext attacks. Primarily of historical and educational interest.</li>
<li><strong>Visual Aid (2x2 Example):</strong><br>
Plaintext: (P1, P2) -> Vector [p1, p2]<sup>T</sup><br>
Key Matrix K = [[a, b], [c, d]]<br>
Ciphertext C = K * P (mod 26)<br>
[c1, c2]<sup>T</sup> = [[a,b],[c,d]] * [p1,p2]<sup>T</sup> (mod 26)<br>
c1 = (a*p1 + b*p2) mod 26<br>
c2 = (c*p1 + d*p2) mod 26
</li>
</ul>`,
learnMore_xsalsa20_title: "XSalsa20-Poly1305",
learnMore_xsalsa20_desc: "XSalsa20-Poly1305 is a modern, high-speed symmetric stream cipher combined with a message authentication code (MAC). XSalsa20 encrypts the data, and Poly1305 provides integrity and authenticity.",
learnMore_xsalsa20_details: `<ul>
<li><strong>Type:</strong> Authenticated Encryption with Associated Data (AEAD) - Symmetric Stream Cipher</li>
<li><strong>Key Size:</strong> 256 bits (32 bytes)</li>
<li><strong>Nonce Size:</strong> 192 bits (24 bytes) - CRITICAL: Never reuse a (key, nonce) pair.</li>
<li><strong>Features:</strong> High speed, good security properties. Suitable for network protocols and data at rest.</li>
<li><strong>Implementation:</strong> The nonce is typically prepended to the ciphertext. This implementation handles nonce generation and management automatically.</li>
</ul>`,
learnMore_ecies_title: "ECIES (Elliptic Curve Integrated Encryption Scheme)",
learnMore_ecies_desc: "ECIES is an asymmetric encryption scheme based on Elliptic Curve Cryptography (ECC). It provides similar security to RSA but with much smaller key sizes, making it efficient for constrained environments.",
learnMore_ecies_details: `<ul>
<li><strong>Type:</strong> Asymmetric Public-Key Cryptosystem (Hybrid)</li>
<li><strong>Key Basis:</strong> Elliptic Curve key pairs (public and private).</li>
<li><strong>Process (Simplified):</strong>
<ol class='list-decimal list-inside ml-4'>
<li>Sender generates an ephemeral EC key pair.</li>
<li>Derives a shared secret using their ephemeral private key and recipient's static public key (ECDH).</li>
<li>Uses a Key Derivation Function (KDF) on the shared secret to get symmetric encryption key and MAC key.</li>
<li>Encrypts data with the symmetric key (e.g., AES).</li>
<li>Computes MAC of ciphertext with MAC key.</li>
<li>Sends ephemeral public key + ciphertext + MAC.</li>
</ol>
</li>
<li><strong>Advantages:</strong> Smaller keys for same security level as RSA, faster computations.</li>
<li><strong>Curves:</strong> Common curves include secp256k1 (used by Bitcoin) and Curve25519.</li>
</ul>`,
// How To Use Guide (English)
howToUse_step1_title: "1. Select Encryption Method",
howToUse_step1_desc: "Choose your desired encryption algorithm from the dropdown menu (e.g., AES-256, RSA). Each algorithm has different properties and key requirements.",
howToUse_step2_title: "2. Provide Input",
howToUse_step2_desc: "Type or paste the text you want to encrypt/decrypt into the 'Input Text' area. Alternatively, click 'Upload Text File' to load content from a .txt, .json, .md, or .csv file.",
howToUse_step3_title: "3. Configure Keys/Parameters",
howToUse_step3_desc: "Depending on the selected algorithm, specific key or parameter fields will appear:<ul><li><strong>AES-256:</strong> Enter a strong password.</li><li><strong>RSA:</strong> Generate a new key pair or paste existing PEM-formatted public/private keys. For encryption, only the public key is needed. For decryption, the private key is required.</li><li><strong>Hill Cipher:</strong> Select matrix size (2x2 or 3x3) and enter the key matrix values (numbers 0-25). Ensure the matrix is invertible mod 26.</li><li><strong>XSalsa20-Poly1305:</strong> Enter a 32-byte Base64 encoded key, or click 'Generate Key' to create a new one. The nonce is handled automatically.</li><li><strong>ECIES:</strong> Select the elliptic curve. Generate a new key pair or paste existing public/private keys (Hex or Base64). For encryption, only the public key is needed. For decryption, the private key is required.</li></ul>",
howToUse_step4_title: "4. Encrypt or Decrypt",
howToUse_step4_desc: "Click the 'Encrypt' or 'Decrypt' button. The result will appear in the 'Output Text' area.",
howToUse_step5_title: "5. Manage Output & Controls",
howToUse_step5_desc: "<ul><li><strong>Copy Output:</strong> Click the copy icon (<i class='ti ti-copy'></i>) next to the output area.</li><li><strong>Key Visibility:</strong> Click the eye icon (<i class='ti ti-eye'></i>) next to password/key fields to toggle visibility.</li><li><strong>Clear All:</strong> Click 'Clear All' to reset all inputs, outputs, and file selections.</li><li><strong>Learn More:</strong> The 'Learn More' tab provides details about the currently selected algorithm.</li></ul>",
howToUse_step6_title: "6. Error Handling",
howToUse_step6_desc: "If an error occurs (e.g., incorrect key format, non-invertible Hill matrix), a descriptive message will appear below the output area.",
fileTooLargeError: "File is too large. Maximum size is 1MB.",
fileReadError: "Error reading file.",
invalidKeyError: "Invalid key provided for the selected algorithm.",
encryptionError: "Encryption failed. Please check your input and key.",
decryptionError: "Decryption failed. Please check your input and key. For AES, ensure the password is correct. For asymmetric ciphers, ensure the correct key (private for decryption) is used.",
hillMatrixNotInvertible: "Hill Cipher key matrix is not invertible modulo 26. Please provide a valid matrix.",
hillMatrixInvalidFormat: "Hill Cipher key matrix has an invalid format or incorrect number of elements.",
hillInputInvalidChars: "Hill Cipher input text must contain only uppercase English letters (A-Z). Spaces and punctuation are not allowed for this implementation.",
rsaKeyGenerationError: "RSA key generation failed.",
eciesKeyGenerationError: "ECIES key generation failed.",
missingPublicKey: "Public key is missing for encryption.",
missingPrivateKey: "Private key is missing for decryption.",
invalidBase64: "Invalid Base64 string provided.",
keyGenerationSuccess: "New key(s) generated successfully and populated in the respective fields.",
},
ja: {
appTitle: "タイタン暗号化ツール",
selectAlgorithm: "暗号化方式を選択:",
algoAES: "AES-256 (対称)",
algoRSA: "RSA (非対称)",
algoHill: "ヒル暗号 (数学的)",
algoXSalsa20: "XSalsa20-Poly1305 (対称ストリーム)",
algoECIES: "ECIES (楕円曲線)",
inputTextLabel: "入力テキスト:",
inputTextPlaceholder: "暗号化/復号化するテキストを入力するか、ファイルをドロップ...",
uploadFileButton: "テキストファイルをアップロード (.txt, .json, .md, .csv)",
aesKeyLabel: "パスワード/キー:",
aesKeyPlaceholder: "AESパスワードを入力 (8文字以上推奨)",
rsaKeySizeLabel: "キーサイズ (ビット):",
rsaGeneratePairButton: "新しいRSAキーペアを生成",
rsaPublicKeyLabel: "公開鍵 (PEM形式):",
rsaPublicKeyPlaceholder: "RSA公開鍵を貼り付けるか、生成してください",
rsaPrivateKeyLabel: "秘密鍵 (PEM形式):",
rsaPrivateKeyPlaceholder: "RSA秘密鍵を貼り付けるか、生成してください",
hillMatrixSizeLabel: "行列サイズ:",
hillKeyMatrixLabel: "鍵行列 (数字0-25、行ごとにスペース/コンマ区切り):",
hillMatrixPlaceholder: "例 2x2: 3 10 5 7 または 3,10\n5,7",
xsalsa20KeyLabel: "キー (32バイト、Base64エンコード):",
xsalsa20KeyPlaceholder: "Base64キーを入力するか、空欄で自動生成",
xsalsa20GenerateKeyButton: "新しいXSalsa20キーを生成",
xsalsa20NonceInfo: "ノンス (一度だけ使用される数値) は自動的に処理され、暗号文の先頭に付加されます。",
eciesCurveLabel: "楕円曲線:",
eciesGeneratePairButton: "新しいECIESキーペアを生成",
eciesPublicKeyLabel: "公開鍵 (HexまたはBase64):",
eciesPublicKeyPlaceholder: "ECIES公開鍵を貼り付けるか、生成してください",
eciesPrivateKeyLabel: "秘密鍵 (HexまたはBase64):",
eciesPrivateKeyPlaceholder: "ECIES秘密鍵を貼り付けるか、生成してください",
encryptButton: "暗号化",
decryptButton: "復号化",
clearButton: "すべてクリア",
outputTextLabel: "出力テキスト:",
outputTextPlaceholder: "暗号化/復号化されたテキストがここに表示されます...",
copyToClipboard: "クリップボードにコピー",
copied: "コピーしました!",
processing: "処理中...",
errorTitle: "エラー",
madeBy: "作成者: Ed Chen",
howToUseButton: "使い方",
learnMoreTab: "詳細情報",
howToUseTab: "使い方",
howToUseGuideTitle: "タイタン暗号化ツールの使い方",
// Learn More Content (Japanese)
learnMore_aes_title: "AES-256 (高度暗号化標準)",
learnMore_aes_desc: "AES-256は、世界中で広く採用されている対称ブロック暗号です。暗号化と復号化の両方に256ビットの鍵を使用し、強力なセキュリティを提供します。「対称」とは、両方のプロセスで同じ鍵が使用されることを意味します。",
learnMore_aes_details: `<ul>
<li><strong>種類:</strong> 対称ブロック暗号</li>
<li><strong>鍵長:</strong> 256ビット</li>
<li><strong>ブロック長:</strong> 128ビット</li>
<li><strong>主な用途:</strong> 機密データの保護、ファイル暗号化、安全な通信</li>
<li><strong>注意:</strong> 入力したパスワードは通常、実際の256ビット暗号鍵を導出するためにハッシュ化されます(例:SHA-256)。パスワードは秘密にしてください。</li>
</ul>`,
learnMore_rsa_title: "RSA (Rivest-Shamir-Adleman)",
learnMore_rsa_desc: "RSAは非対称暗号アルゴリズムです。公開鍵(暗号化用)と秘密鍵(復号化用)のペアを使用します。公開鍵は自由に共有できますが、秘密鍵は秘密に保つ必要があります。",
learnMore_rsa_details: `<ul>
<li><strong>種類:</strong> 非対称公開鍵暗号方式</li>
<li><strong>鍵長:</strong> 一般的に2048または4096ビット。鍵長が長いほどセキュリティは向上しますが、処理速度は低下します。</li>
<li><strong>処理:</strong> 公開鍵で暗号化、秘密鍵で復号化。(デジタル署名にも使用:秘密鍵で署名、公開鍵で検証)</li>
<li><strong>鍵形式:</strong> 多くの場合PEM(Privacy Enhanced Mail)形式。</li>
<li><strong>注意:</strong> RSAはAESのような対称暗号より遅いため、対称鍵を暗号化し、その対称鍵で大量のデータを暗号化するためによく使用されます(ハイブリッド暗号)。</li>
</ul>`,
learnMore_hill_title: "ヒル暗号",
learnMore_hill_desc: "ヒル暗号は線形代数に基づく多重換字暗号です。文字のブロック(通常2または3文字)を行列乗算を使用して暗号化します。鍵は正方行列です。",
learnMore_hill_details: `<ul>
<li><strong>種類:</strong> 多重換字暗号(対称)</li>
<li><strong>鍵:</strong> N x N の整数行列(英語アルファベットA-Zの場合、法26)。</li>
<li><strong>処理:</strong> N文字のブロックをベクトルに変換し、鍵行列を乗算します(法26)。復号化には鍵行列の逆行列(法26)を使用します。</li>
<li><strong>要件:</strong> 復号化を可能にするには、鍵行列が法26で可逆でなければなりません。つまり、その行列式が26と互いに素である必要があります(2または13で割り切れない)。</li>
<li><strong>セキュリティ:</strong> 既知平文攻撃に対して脆弱です。主に歴史的および教育的関心があります。</li>
<li><strong>図解(2x2の例):</strong><br>
平文: (P1, P2) -> ベクトル [p1, p2]<sup>T</sup><br>
鍵行列 K = [[a, b], [c, d]]<br>
暗号文 C = K * P (mod 26)<br>
[c1, c2]<sup>T</sup> = [[a,b],[c,d]] * [p1,p2]<sup>T</sup> (mod 26)<br>
c1 = (a*p1 + b*p2) mod 26<br>
c2 = (c*p1 + d*p2) mod 26
</li>
</ul>`,
learnMore_xsalsa20_title: "XSalsa20-Poly1305",
learnMore_xsalsa20_desc: "XSalsa20-Poly1305は、最新の高速対称ストリーム暗号とメッセージ認証コード(MAC)を組み合わせたものです。XSalsa20がデータを暗号化し、Poly1305が完全性と認証性を提供します。",
learnMore_xsalsa20_details: `<ul>
<li><strong>種類:</strong> 認証付き暗号(AEAD) - 対称ストリーム暗号</li>
<li><strong>鍵長:</strong> 256ビット(32バイト)</li>
<li><strong>ノンス長:</strong> 192ビット(24バイト) - 重要:同じ(鍵、ノンス)のペアを再利用しないでください。</li>
<li><strong>特徴:</strong> 高速、優れたセキュリティ特性。ネットワークプロトコルや保存データに適しています。</li>
<li><strong>実装:</strong> ノンスは通常、暗号文の先頭に付加されます。この実装では、ノンスの生成と管理を自動的に行います。</li>
</ul>`,
learnMore_ecies_title: "ECIES (楕円曲線統合暗号方式)",
learnMore_ecies_desc: "ECIESは楕円曲線暗号(ECC)に基づく非対称暗号方式です。RSAと同等のセキュリティをはるかに小さい鍵サイズで提供するため、制約のある環境で効率的です。",
learnMore_ecies_details: `<ul>
<li><strong>種類:</strong> 非対称公開鍵暗号方式(ハイブリッド)</li>
<li><strong>鍵の基礎:</strong> 楕円曲線キーペア(公開鍵と秘密鍵)。</li>
<li><strong>処理(簡略版):</strong>
<ol class='list-decimal list-inside ml-4'>
<li>送信者は一時的なECキーペアを生成します。</li>
<li>自身の一時秘密鍵と受信者の静的公開鍵を使用して共有秘密を導出します(ECDH)。</li>
<li>共有秘密に対して鍵導出関数(KDF)を使用して、対称暗号鍵とMAC鍵を取得します。</li>
<li>対称鍵(例:AES)でデータを暗号化します。</li>
<li>MAC鍵で暗号文のMACを計算します。</li>
<li>一時公開鍵 + 暗号文 + MAC を送信します。</li>
</ol>
</li>
<li><strong>利点:</strong> RSAと同じセキュリティレベルで鍵が小さく、計算が高速です。</li>
<li><strong>曲線:</strong> 一般的な曲線にはsecp256k1(ビットコインで使用)やCurve25519があります。</li>
</ul>`,
// How To Use Guide (Japanese)
howToUse_step1_title: "1. 暗号化方式の選択",
howToUse_step1_desc: "ドロップダウンメニューから希望の暗号化アルゴリズム(例:AES-256、RSA)を選択します。各アルゴリズムには異なる特性と鍵の要件があります。",
howToUse_step2_title: "2. 入力の提供",
howToUse_step2_desc: "「入力テキスト」エリアに暗号化/復号化したいテキストを入力または貼り付けます。または、「テキストファイルをアップロード」をクリックして、.txt、.json、.md、または.csvファイルからコンテンツを読み込みます。",
howToUse_step3_title: "3. 鍵/パラメータの設定",
howToUse_step3_desc: "選択したアルゴリズムに応じて、特定の鍵またはパラメータフィールドが表示されます:<ul><li><strong>AES-256:</strong> 強力なパスワードを入力します。</li><li><strong>RSA:</strong> 新しいキーペアを生成するか、既存のPEM形式の公開/秘密鍵を貼り付けます。暗号化には公開鍵のみが必要です。復号化には秘密鍵が必要です。</li><li><strong>ヒル暗号:</strong> 行列サイズ(2x2または3x3)を選択し、鍵行列の値を入力します(数字0-25)。行列が法26で可逆であることを確認してください。</li><li><strong>XSalsa20-Poly1305:</strong> 32バイトのBase64エンコードされた鍵を入力するか、「キーを生成」をクリックして新しい鍵を作成します。ノンスは自動的に処理されます。</li><li><strong>ECIES:</strong> 楕円曲線を選択します。新しいキーペアを生成するか、既存の公開/秘密鍵(HexまたはBase64)を貼り付けます。暗号化には公開鍵のみが必要です。復号化には秘密鍵が必要です。</li></ul>",
howToUse_step4_title: "4. 暗号化または復号化",
howToUse_step4_desc: "「暗号化」または「復号化」ボタンをクリックします。結果は「出力テキスト」エリアに表示されます。",
howToUse_step5_title: "5. 出力とコントロールの管理",
howToUse_step5_desc: "<ul><li><strong>出力のコピー:</strong> 出力エリアの隣にあるコピーアイコン(<i class='ti ti-copy'></i>)をクリックします。</li><li><strong>キーの可視性:</strong> パスワード/キーフィールドの隣にある目のアイコン(<i class='ti ti-eye'></i>)をクリックして、可視性を切り替えます。</li><li><strong>すべてクリア:</strong> 「すべてクリア」をクリックして、すべての入力、出力、およびファイル選択をリセットします。</li><li><strong>詳細情報:</strong> 「詳細情報」タブには、現在選択されているアルゴリズムに関する詳細が表示されます。</li></ul>",
howToUse_step6_title: "6. エラー処理",
howToUse_step6_desc: "エラーが発生した場合(例:不正なキー形式、ヒル行列が非可逆)、出力エリアの下に説明的なメッセージが表示されます。",
fileTooLargeError: "ファイルが大きすぎます。最大サイズは1MBです。",
fileReadError: "ファイルの読み取り中にエラーが発生しました。",
invalidKeyError: "選択されたアルゴリズムに対して無効なキーが提供されました。",
encryptionError: "暗号化に失敗しました。入力とキーを確認してください。",
decryptionError: "復号化に失敗しました。入力とキーを確認してください。AESの場合、パスワードが正しいことを確認してください。非対称暗号の場合、正しいキー(復号化には秘密鍵)が使用されていることを確認してください。",
hillMatrixNotInvertible: "ヒル暗号の鍵行列は法26で可逆ではありません。有効な行列を提供してください。",
hillMatrixInvalidFormat: "ヒル暗号の鍵行列の形式が無効か、要素数が正しくありません。",
hillInputInvalidChars: "ヒル暗号の入力テキストには、大文字の英字(A-Z)のみを含める必要があります。この実装では、スペースや句読点は使用できません。",
rsaKeyGenerationError: "RSAキーの生成に失敗しました。",
eciesKeyGenerationError: "ECIESキーの生成に失敗しました。",
missingPublicKey: "暗号化のための公開鍵がありません。",
missingPrivateKey: "復号化のための秘密鍵がありません。",
invalidBase64: "無効なBase64文字列が提供されました。",
keyGenerationSuccess: "新しいキーが正常に生成され、それぞれのフィールドに入力されました。",
},
'zh-TW': {
appTitle: "泰坦加密器",
selectAlgorithm: "選擇加密方法:",
algoAES: "AES-256 (對稱式)",
algoRSA: "RSA (非對稱式)",
algoHill: "希爾密碼 (數學)",
algoXSalsa20: "XSalsa20-Poly1305 (對稱式流)",
algoECIES: "ECIES (橢圓曲線)",
inputTextLabel: "輸入文字:",
inputTextPlaceholder: "輸入要加密/解密的文字或拖放檔案...",
uploadFileButton: "上傳文字檔案 (.txt, .json, .md, .csv)",
aesKeyLabel: "密碼/金鑰:",
aesKeyPlaceholder: "輸入 AES 密碼 (建議至少8個字元)",
rsaKeySizeLabel: "金鑰大小 (位元):",
rsaGeneratePairButton: "產生新的 RSA 金鑰對",
rsaPublicKeyLabel: "公鑰 (PEM 格式):",
rsaPublicKeyPlaceholder: "貼上 RSA 公鑰或由此產生",
rsaPrivateKeyLabel: "私鑰 (PEM 格式):",
rsaPrivateKeyPlaceholder: "貼上 RSA 私鑰或由此產生",
hillMatrixSizeLabel: "矩陣大小:",
hillKeyMatrixLabel: "金鑰矩陣 (數字 0-25,每列以空格/逗號分隔):",
hillMatrixPlaceholder: "例如 2x2: 3 10 5 7 或 3,10\n5,7",
xsalsa20KeyLabel: "金鑰 (32 位元組,Base64 編碼):",
xsalsa20KeyPlaceholder: "輸入 Base64 金鑰或留空以自動產生",
xsalsa20GenerateKeyButton: "產生新的 XSalsa20 金鑰",
xsalsa20NonceInfo: "Nonce (臨時數) 會自動處理並附加到密文的前面。",
eciesCurveLabel: "橢圓曲線:",
eciesGeneratePairButton: "產生新的 ECIES 金鑰對",
eciesPublicKeyLabel: "公鑰 (十六進制或 Base64):",
eciesPublicKeyPlaceholder: "貼上 ECIES 公鑰或由此產生",
eciesPrivateKeyLabel: "私鑰 (十六進制或 Base64):",
eciesPrivateKeyPlaceholder: "貼上 ECIES 私鑰或由此產生",
encryptButton: "加密",
decryptButton: "解密",
clearButton: "全部清除",
outputTextLabel: "輸出文字:",
outputTextPlaceholder: "加密/解密的文字將顯示在此處...",
copyToClipboard: "複製到剪貼簿",
copied: "已複製!",
processing: "處理中...",
errorTitle: "錯誤",
madeBy: "開發者: Ed Chen",
howToUseButton: "使用說明",
learnMoreTab: "了解更多",
howToUseTab: "使用說明",
howToUseGuideTitle: "泰坦加密器使用指南",
// Learn More Content (Traditional Chinese)
learnMore_aes_title: "AES-256 (進階加密標準)",
learnMore_aes_desc: "AES-256 是一種全球廣泛採用的對稱區塊加密法。它使用 256 位元的金鑰進行加密和解密,提供強大的安全性。「對稱」意味著加密和解密過程使用相同的金鑰。",
learnMore_aes_details: `<ul>
<li><strong>類型:</strong> 對稱區塊加密法</li>
<li><strong>金鑰長度:</strong> 256 位元</li>
<li><strong>區塊長度:</strong> 128 位元</li>
<li><strong>常見用途:</strong> 保護敏感資料、檔案加密、安全通訊。</li>
<li><strong>注意:</strong> 您輸入的密碼通常會被雜湊處理 (例如使用 SHA-256) 以衍生出實際的 256 位元加密金鑰。請妥善保管您的密碼。</li>
</ul>`,
learnMore_rsa_title: "RSA (Rivest-Shamir-Adleman)",
learnMore_rsa_desc: "RSA 是一種非對稱加密演算法。它使用一對金鑰:公鑰用於加密,私鑰用於解密。公鑰可以公開分享,而私鑰必須保密。",
learnMore_rsa_details: `<ul>
<li><strong>類型:</strong> 非對稱公開金鑰密碼系統</li>
<li><strong>金鑰長度:</strong> 常見為 2048 或 4096 位元。較長的金鑰提供更高的安全性,但速度較慢。</li>
<li><strong>過程:</strong> 使用公鑰加密,使用私鑰解密。(也用於數位簽章:使用私鑰簽署,使用公鑰驗證)。</li>
<li><strong>金鑰格式:</strong> 通常為 PEM (Privacy Enhanced Mail) 格式。</li>
<li><strong>注意:</strong> RSA 比 AES 等對稱加密法慢,因此通常用於加密對稱金鑰,然後再用該對稱金鑰加密大量資料 (混合加密)。</li>
</ul>`,
learnMore_hill_title: "希爾密碼 (Hill Cipher)",
learnMore_hill_desc: "希爾密碼是一種基於線性代數的多字母替換密碼。它使用矩陣乘法來加密字母區塊 (通常是 2 或 3 個字母)。金鑰是一個方陣。",
learnMore_hill_details: `<ul>
<li><strong>類型:</strong> 多字母替換密碼 (對稱式)</li>
<li><strong>金鑰:</strong> 一個 N x N 的整數矩陣 (對於英文字母 A-Z,模 26)。</li>
<li><strong>過程:</strong> 將 N 個字母的區塊轉換為向量,然後乘以金鑰矩陣 (模 26)。解密時使用金鑰矩陣的逆矩陣 (模 26)。</li>
<li><strong>要求:</strong> 金鑰矩陣必須在模 26 下可逆,解密才可能。這表示其行列式值必須與 26 互質 (即不能被 2 或 13 整除)。</li>
<li><strong>安全性:</strong> 易受已知明文攻擊。主要具有歷史和教育意義。</li>
<li><strong>視覺輔助 (2x2 範例):</strong><br>
明文: (P1, P2) -> 向量 [p1, p2]<sup>T</sup><br>
金鑰矩陣 K = [[a, b], [c, d]]<br>
密文 C = K * P (mod 26)<br>
[c1, c2]<sup>T</sup> = [[a,b],[c,d]] * [p1,p2]<sup>T</sup> (mod 26)<br>
c1 = (a*p1 + b*p2) mod 26<br>
c2 = (c*p1 + d*p2) mod 26
</li>
</ul>`,
learnMore_xsalsa20_title: "XSalsa20-Poly1305",
learnMore_xsalsa20_desc: "XSalsa20-Poly1305 是一種現代高速的對稱式流加密法,結合了訊息鑑別碼 (MAC)。XSalsa20 加密資料,而 Poly1305 提供完整性和真實性驗證。",
learnMore_xsalsa20_details: `<ul>
<li><strong>類型:</strong> 帶有關聯資料的驗證加密 (AEAD) - 對稱式流加密法</li>
<li><strong>金鑰長度:</strong> 256 位元 (32 位元組)</li>
<li><strong>Nonce 長度:</strong> 192 位元 (24 位元組) - 極重要:切勿重複使用相同的 (金鑰, Nonce) 組合。</li>
<li><strong>特性:</strong> 高速度、良好的安全性。適用於網路協定和靜態資料。</li>
<li><strong>實作:</strong> Nonce 通常附加在密文的前面。此實作會自動處理 Nonce 的產生與管理。</li>
</ul>`,
learnMore_ecies_title: "ECIES (橢圓曲線整合加密機制)",
learnMore_ecies_desc: "ECIES 是一種基於橢圓曲線密碼學 (ECC) 的非對稱加密機制。它能以遠小於 RSA 的金鑰長度提供同等級的安全性,使其在資源受限的環境中非常高效。",
learnMore_ecies_details: `<ul>
<li><strong>類型:</strong> 非對稱公開金鑰密碼系統 (混合式)</li>
<li><strong>金鑰基礎:</strong> 橢圓曲線金鑰對 (公鑰和私鑰)。</li>
<li><strong>過程 (簡化版):</strong>
<ol class='list-decimal list-inside ml-4'>
<li>傳送方產生一個臨時的 EC 金鑰對。</li>
<li>使用其臨時私鑰和接收方的靜態公鑰導出共享秘密 (ECDH)。</li>
<li>對共享秘密使用金鑰派生函數 (KDF) 以獲得對稱加密金鑰和 MAC 金鑰。</li>
<li>使用對稱金鑰 (例如 AES) 加密資料。</li>
<li>使用 MAC 金鑰計算密文的 MAC。</li>
<li>傳送臨時公鑰 + 密文 + MAC。</li>
</ol>
</li>
<li><strong>優點:</strong> 金鑰較小即可達到與 RSA 相同的安全等級,運算速度更快。</li>
<li><strong>曲線:</strong> 常見曲線包括 secp256k1 (比特幣使用) 和 Curve25519。</li>
</ul>`,
// How To Use Guide (Traditional Chinese)
howToUse_step1_title: "1. 選擇加密方法",
howToUse_step1_desc: "從下拉選單中選擇您想要的加密演算法(例如:AES-256、RSA)。每種演算法都有不同的特性和金鑰要求。",
howToUse_step2_title: "2. 提供輸入內容",
howToUse_step2_desc: "在「輸入文字」區域中輸入或貼上您要加密/解密的文字。或者,點擊「上傳文字檔案」以從 .txt、.json、.md 或 .csv 檔案載入內容。",
howToUse_step3_title: "3. 設定金鑰/參數",
howToUse_step3_desc: "根據所選的演算法,將會出現特定的金鑰或參數欄位:<ul><li><strong>AES-256:</strong> 輸入一個強度高的密碼。</li><li><strong>RSA:</strong> 產生新的金鑰對或貼上現有的 PEM 格式公鑰/私鑰。加密時只需要公鑰。解密時需要私鑰。</li><li><strong>希爾密碼:</strong> 選擇矩陣大小(2x2 或 3x3)並輸入金鑰矩陣值(數字 0-25)。確保該矩陣在模 26 下是可逆的。</li><li><strong>XSalsa20-Poly1305:</strong> 輸入一個 32 位元組的 Base64 編碼金鑰,或點擊「產生金鑰」以建立新的金鑰。Nonce 會自動處理。</li><li><strong>ECIES:</strong> 選擇橢圓曲線。產生新的金鑰對或貼上現有的公鑰/私鑰(十六進制或 Base64)。加密時只需要公鑰。解密時需要私鑰。</li></ul>",
howToUse_step4_title: "4. 執行加密或解密",
howToUse_step4_desc: "點擊「加密」或「解密」按鈕。結果將顯示在「輸出文字」區域中。",
howToUse_step5_title: "5. 管理輸出與控制項",
howToUse_step5_desc: "<ul><li><strong>複製輸出:</strong> 點擊輸出區域旁邊的複製圖示 (<i class='ti ti-copy'></i>)。</li><li><strong>金鑰可見性:</strong> 點擊密碼/金鑰欄位旁邊的眼睛圖示 (<i class='ti ti-eye'></i>) 以切換可見性。</li><li><strong>全部清除:</strong> 點擊「全部清除」以重設所有輸入、輸出和檔案選擇。</li><li><strong>了解更多:</strong> 「了解更多」分頁提供有關當前所選演算法的詳細資訊。</li></ul>",
howToUse_step6_title: "6. 錯誤處理",
howToUse_step6_desc: "如果發生錯誤(例如:金鑰格式不正確、希爾矩陣不可逆),輸出區域下方將顯示描述性訊息。",
fileTooLargeError: "檔案過大。最大允許 1MB。",
fileReadError: "讀取檔案時發生錯誤。",
invalidKeyError: "為所選演算法提供了無效的金鑰。",
encryptionError: "加密失敗。請檢查您的輸入和金鑰。",
decryptionError: "解密失敗。請檢查您的輸入和金鑰。對於 AES,請確保密碼正確。對於非對稱加密,請確保使用正確的金鑰(解密時為私鑰)。",
hillMatrixNotInvertible: "希爾密碼的金鑰矩陣在模 26 下不可逆。請提供有效的矩陣。",
hillMatrixInvalidFormat: "希爾密碼的金鑰矩陣格式無效或元素數量不正確。",
hillInputInvalidChars: "希爾密碼輸入文字必須僅包含大寫英文字母 (A-Z)。此實作不允許空格和標點符號。",
rsaKeyGenerationError: "RSA 金鑰產生失敗。",
eciesKeyGenerationError: "ECIES 金鑰產生失敗。",
missingPublicKey: "加密所需的公鑰遺失。",
missingPrivateKey: "解密所需的私鑰遺失。",
invalidBase64: "提供了無效的 Base64 字串。",
keyGenerationSuccess: "新金鑰已成功產生並填入相應欄位。",
}
},
// --- Initialization ---
init() {
// Cache DOM elements
this.elements.languageSwitcher = document.getElementById('language-switcher');
this.elements.encryptionMethodSelect = document.getElementById('encryption-method');
this.elements.inputText = document.getElementById('input-text');
this.elements.fileInput = document.getElementById('file-input');
this.elements.fileNameDisplay = document.getElementById('file-name');
this.elements.outputText = document.getElementById('output-text');
this.elements.encryptBtn = document.getElementById('encrypt-btn');
this.elements.decryptBtn = document.getElementById('decrypt-btn');
this.elements.clearBtn = document.getElementById('clear-btn');
this.elements.copyOutputBtn = document.getElementById('copy-output-btn');
this.elements.algoSpecificControlsContainer = document.getElementById('algo-specific-controls');
this.elements.aesControls = document.getElementById('aes-controls');
this.elements.rsaControls = document.getElementById('rsa-controls');
this.elements.hillControls = document.getElementById('hill-controls');
this.elements.xsalsa20Controls = document.getElementById('xsalsa20-controls');
this.elements.eciesControls = document.getElementById('ecies-controls');
this.elements.aesKeyInput = document.getElementById('aes-key');
this.elements.rsaKeySizeSelect = document.getElementById('rsa-key-size');
this.elements.rsaGenerateKeysBtn = document.getElementById('rsa-generate-keys');
this.elements.rsaPublicKeyInput = document.getElementById('rsa-public-key');
this.elements.rsaPrivateKeyInput = document.getElementById('rsa-private-key');
this.elements.hillMatrixSizeSelect = document.getElementById('hill-matrix-size');
this.elements.hillMatrixInputContainer = document.getElementById('hill-matrix-input');
this.elements.xsalsa20KeyInput = document.getElementById('xsalsa20-key');
this.elements.xsalsa20GenerateKeyBtn = document.getElementById('xsalsa20-generate-key');
this.elements.eciesCurveSelect = document.getElementById('ecies-curve');
this.elements.eciesGenerateKeysBtn = document.getElementById('ecies-generate-keys');
this.elements.eciesPublicKeyInput = document.getElementById('ecies-public-key');
this.elements.eciesPrivateKeyInput = document.getElementById('ecies-private-key');
this.elements.errorMessageArea = document.getElementById('error-message-area');
this.elements.errorMessageText = document.getElementById('error-message-text');
this.elements.loadingOverlay = document.getElementById('loading-overlay');
this.elements.loadingText = document.getElementById('loading-text');
this.elements.learnMoreTitle = document.getElementById('learn-more-title');
this.elements.learnMoreDescription = document.getElementById('learn-more-description');
this.elements.learnMoreDetails = document.getElementById('learn-more-details');
this.elements.howToUseContent = document.getElementById('how-to-use-content');
this.elements.howToUseBtn = document.getElementById('howToUseBtn');
this.elements.genericModal = document.getElementById('genericModal');
this.elements.genericModalTitle = document.getElementById('genericModalTitle');
this.elements.genericModalBody = document.getElementById('genericModalBody');
// Load saved language or default to browser language if supported
const savedLang = localStorage.getItem('titanCryptoLang');
if (savedLang && this.translations[savedLang]) {
this.config.currentLanguage = savedLang;
} else {
const browserLang = navigator.language.slice(0, 2);
if (this.translations[browserLang]) {
this.config.currentLanguage = browserLang;
} else if (navigator.language === 'zh-TW' && this.translations['zh-TW']) {
this.config.currentLanguage = 'zh-TW';
}
}
this.elements.languageSwitcher.value = this.config.currentLanguage;
// Initial UI setup
this.updateLanguage();
this.updateAlgorithmControls();
this.renderHillMatrixInputs();
this.initMatrixBackground();
// Add event listeners
this.elements.languageSwitcher.addEventListener('change', (e) => {
this.config.currentLanguage = e.target.value;
localStorage.setItem('titanCryptoLang', this.config.currentLanguage);
this.updateLanguage();
});
this.elements.encryptionMethodSelect.addEventListener('change', (e) => {
this.config.currentAlgorithm = e.target.value;
this.updateAlgorithmControls();
// this.updateLearnMoreContent(); // updateLearnMoreContent is called within updateAlgorithmControls
});
this.elements.encryptBtn.addEventListener('click', () => this.handleEncrypt());
this.elements.decryptBtn.addEventListener('click', () => this.handleDecrypt());
this.elements.clearBtn.addEventListener('click', () => this.clearAll());
this.elements.copyOutputBtn.addEventListener('click', () => this.copyOutput());
this.elements.fileInput.addEventListener('change', (e) => this.handleFileUpload(e));
// Allow dropping files onto the textarea
this.elements.inputText.addEventListener('dragover', (e) => {
e.preventDefault();
e.stopPropagation();
e.target.classList.add('border-cyan-400', 'ring-2', 'ring-cyan-400');
});
this.elements.inputText.addEventListener('dragleave', (e) => {
e.preventDefault();
e.stopPropagation();
e.target.classList.remove('border-cyan-400', 'ring-2', 'ring-cyan-400');
});
this.elements.inputText.addEventListener('drop', (e) => {
e.preventDefault();
e.stopPropagation();
e.target.classList.remove('border-cyan-400', 'ring-2', 'ring-cyan-400');
if (e.dataTransfer.files && e.dataTransfer.files[0]) {
this.handleFileUpload({ target: { files: e.dataTransfer.files } });
}
});
// RSA specific listeners
this.elements.rsaKeySizeSelect.addEventListener('change', (e) => {
this.config.rsaKeySize = parseInt(e.target.value);
});
this.elements.rsaGenerateKeysBtn.addEventListener('click', () => this.generateRsaKeyPair());
// Hill Cipher specific listeners
this.elements.hillMatrixSizeSelect.addEventListener('change', (e) => {
this.config.hillMatrixSize = parseInt(e.target.value);
this.renderHillMatrixInputs();
});
// XSalsa20 specific listeners
this.elements.xsalsa20GenerateKeyBtn.addEventListener('click', () => this.generateXsalsa20Key());
// ECIES specific listeners
this.elements.eciesCurveSelect.addEventListener('change', (e) => {
this.config.eciesCurve = e.target.value;
});
this.elements.eciesGenerateKeysBtn.addEventListener('click', () => this.generateEciesKeyPair());
this.elements.howToUseBtn.addEventListener('click', () => {
this.elements.genericModalTitle.innerText = this.translate('howToUseGuideTitle');
this.elements.genericModalBody.innerHTML = this.getHowToUseGuideHTML();
this.openModal('genericModal');
});
},
// --- Localization ---
translate(key, params = {}) {
const langSet = this.translations[this.config.currentLanguage] || this.translations.en;
let text = langSet[key] || key;
for (const pKey in params) {
text = text.replace(new RegExp(`{${pKey}}`, 'g'), params[pKey]);
}
return text;
},
updateLanguage() {
document.documentElement.lang = this.config.currentLanguage.startsWith('zh') ? 'zh-TW' : this.config.currentLanguage; // Set HTML lang attribute
document.querySelectorAll('[data-translate]').forEach(el => {
const key = el.getAttribute('data-translate');
el.innerText = this.translate(key);
});
document.querySelectorAll('[data-translate-placeholder]').forEach(el => {
const key = el.getAttribute('data-translate-placeholder');
el.placeholder = this.translate(key);
});
this.updateLearnMoreContent(); // Update dynamic content
this.updateHowToUseGuide(); // Update How to Use guide in tab
},
// --- UI Updates ---
updateAlgorithmControls() {
// Hide all algo-specific controls first
this.elements.aesControls.classList.add('hidden');
this.elements.rsaControls.classList.add('hidden');
this.elements.hillControls.classList.add('hidden');
this.elements.xsalsa20Controls.classList.add('hidden');
this.elements.eciesControls.classList.add('hidden');
// Show controls for the selected algorithm
switch (this.config.currentAlgorithm) {
case 'aes':
this.elements.aesControls.classList.remove('hidden');
break;
case 'rsa':
this.elements.rsaControls.classList.remove('hidden');
break;
case 'hill':
this.elements.hillControls.classList.remove('hidden');
this.renderHillMatrixInputs(); // Ensure matrix inputs are correct size
break;
case 'xsalsa20':
this.elements.xsalsa20Controls.classList.remove('hidden');
break;
case 'ecies':
this.elements.eciesControls.classList.remove('hidden');
break;
}
this.updateLearnMoreContent(); // Also update "Learn More" when algo changes
},
renderHillMatrixInputs() {
const size = this.config.hillMatrixSize;
this.elements.hillMatrixInputContainer.innerHTML = ''; // Clear previous inputs
this.elements.hillMatrixInputContainer.className = `hill-matrix-grid grid-cols-${size}`; // Update grid columns
for (let i = 0; i < size * size; i++) {
const input = document.createElement('input');
input.type = 'number';
input.min = '0';
input.max = '25';
input.className = 'titan-input text-center';
// input.placeholder = (i % size === 0 && i > 0) ? `R${Math.floor(i/size)+1}` : ''; // Row hint - removed for cleaner look
this.elements.hillMatrixInputContainer.appendChild(input);
}
},
updateLearnMoreContent() {
const algo = this.config.currentAlgorithm;
this.elements.learnMoreTitle.innerText = this.translate(`learnMore_${algo}_title`);
this.elements.learnMoreDescription.innerHTML = this.translate(`learnMore_${algo}_desc`); // Use innerHTML for potential formatting
this.elements.learnMoreDetails.innerHTML = this.translate(`learnMore_${algo}_details`); // Use innerHTML for potential formatting
},
updateHowToUseGuide() {
this.elements.howToUseContent.innerHTML = this.getHowToUseGuideHTML();
},
getHowToUseGuideHTML() {
let html = '';
for (let i = 1; i <= 6; i++) {
html += `<h4 class="text-lg font-semibold text-cyan-300 mt-4 mb-1">${this.translate(`howToUse_step${i}_title`)}</h4>`; // Adjusted margins
html += `<div class="text-gray-300">${this.translate(`howToUse_step${i}_desc`)}</div>`; // Wrapped desc in div for styling consistency
}
return html;
},
togglePasswordVisibility(inputId) {
const input = document.getElementById(inputId);
const icon = input.nextElementSibling.querySelector('i');
if (input.type === "password") {
input.type = "text";
icon.classList.remove('ti-eye');
icon.classList.add('ti-eye-off');
} else {
input.type = "password";
icon.classList.remove('ti-eye-off');
icon.classList.add('ti-eye');
}
},
showLoading(textKey = 'processing') {
this.elements.loadingText.innerText = this.translate(textKey);
this.elements.loadingOverlay.classList.add('active');
},
hideLoading() {
this.elements.loadingOverlay.classList.remove('active');
},
showError(messageKey, params = {}) {
this.elements.errorMessageText.innerText = this.translate(messageKey, params);
this.elements.errorMessageArea.classList.remove('hidden');
},
hideError() {
this.elements.errorMessageArea.classList.add('hidden');
},
clearAll() {
this.elements.inputText.value = '';
this.elements.outputText.value = '';
this.elements.aesKeyInput.value = '';
this.elements.rsaPublicKeyInput.value = '';
this.elements.rsaPrivateKeyInput.value = '';
this.elements.xsalsa20KeyInput.value = '';
this.elements.eciesPublicKeyInput.value = '';
this.elements.eciesPrivateKeyInput.value = '';
this.elements.fileInput.value = ''; // Reset file input
this.elements.fileNameDisplay.textContent = '';
this.renderHillMatrixInputs(); // Clears Hill matrix inputs
this.hideError();
},
copyOutput() {
if (!this.elements.outputText.value) return;
navigator.clipboard.writeText(this.elements.outputText.value)
.then(() => {
const originalTitle = this.elements.copyOutputBtn.title;
const originalIcon = this.elements.copyOutputBtn.innerHTML;
this.elements.copyOutputBtn.innerHTML = `<i class="ti ti-check text-green-400 !mr-0"></i>`;
this.elements.copyOutputBtn.title = this.translate('copied');
setTimeout(() => {
this.elements.copyOutputBtn.innerHTML = originalIcon;
this.elements.copyOutputBtn.title = originalTitle;
}, 1500);
})
.catch(err => this.showError('Error copying text: ' + err));
},
handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
if (file.size > 1024 * 1024) { // 1MB limit
this.showError('fileTooLargeError');
this.elements.fileInput.value = ''; // Reset file input
this.elements.fileNameDisplay.textContent = '';
return;
}
const reader = new FileReader();
reader.onload = (e) => {
this.elements.inputText.value = e.target.result;
this.elements.fileNameDisplay.textContent = file.name;
};
reader.onerror = () => {
this.showError('fileReadError');
this.elements.fileNameDisplay.textContent = '';
};
reader.readAsText(file);
},
// --- Tab and Modal Management ---
openTab(event, tabId, clickedButton) {
let i, tabcontent, tablinks;
// Get the parent container of the tabs (either the right panel or a modal)
const parent = clickedButton.closest('.glass-panel, .modal-content');
tabcontent = parent.querySelectorAll(".tab-content");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].classList.remove("active");
tabcontent[i].style.display = "none";
}
tablinks = parent.querySelectorAll(".tab-button");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].classList.remove("active");
}
const targetTabContent = document.getElementById(tabId);
if (targetTabContent) {
targetTabContent.style.display = "block";
targetTabContent.classList.add("active");
}
clickedButton.classList.add("active");
},
openModal(modalId) {
document.getElementById(modalId).classList.add('active');
},
closeModal(modalId) {
document.getElementById(modalId).classList.remove('active');
},
// --- Core Encryption/Decryption Handlers ---
async handleEncrypt() {
this.showLoading();
this.hideError();
const inputText = this.elements.inputText.value;
if (!inputText) {
// Do not use this.showError here, it's for translation keys
this.elements.errorMessageText.innerText = "Input text cannot be empty.";
this.elements.errorMessageArea.classList.remove('hidden');
this.hideLoading();
return;
}
try {
let result = '';
switch (this.config.currentAlgorithm) {
case 'aes':
result = this.aesEncrypt(inputText, this.elements.aesKeyInput.value);
break;
case 'rsa':
result = this.rsaEncrypt(inputText, this.elements.rsaPublicKeyInput.value);
break;
case 'hill':
result = this.hillEncrypt(inputText, this.getHillMatrix());
break;
case 'xsalsa20':
result = this.xsalsa20Encrypt(inputText, this.elements.xsalsa20KeyInput.value);
break;
case 'ecies':
result = await this.eciesEncrypt(inputText, this.elements.eciesPublicKeyInput.value);
break;
default:
// This should be a translation key if it's a user-facing error
this.showError('Unsupported algorithm selected.'); // Or a proper translation key
this.hideLoading();
return;
}
this.elements.outputText.value = result;
} catch (error) {
console.error("Encryption error:", error);
// Check if error is a translation key string
if (typeof error === 'string' && (this.translations.en[error] || (this.translations[this.config.currentLanguage] && this.translations[this.config.currentLanguage][error]))) {
this.showError(error);
} else { // Otherwise, it's likely an Error object or a non-translatable string
this.showError('encryptionError', { details: error.message || String(error) });
}
} finally {
this.hideLoading();
}
},
async handleDecrypt() {
this.showLoading();
this.hideError();
const inputText = this.elements.inputText.value; // This is ciphertext now
if (!inputText) {
this.elements.errorMessageText.innerText = "Input text cannot be empty.";
this.elements.errorMessageArea.classList.remove('hidden');
this.hideLoading();
return;
}
try {
let result = '';
switch (this.config.currentAlgorithm) {
case 'aes':
result = this.aesDecrypt(inputText, this.elements.aesKeyInput.value);
break;
case 'rsa':
result = this.rsaDecrypt(inputText, this.elements.rsaPrivateKeyInput.value);
break;
case 'hill':
result = this.hillDecrypt(inputText, this.getHillMatrix());
break;
case 'xsalsa20':
result = this.xsalsa20Decrypt(inputText, this.elements.xsalsa20KeyInput.value);
break;
case 'ecies':
result = await this.eciesDecrypt(inputText, this.elements.eciesPrivateKeyInput.value);
break;
default:
this.showError('Unsupported algorithm selected.'); // Or a proper translation key
this.hideLoading();
return;
}
this.elements.outputText.value = result;
} catch (error) {
console.error("Decryption error:", error);
if (typeof error === 'string' && (this.translations.en[error] || (this.translations[this.config.currentLanguage] && this.translations[this.config.currentLanguage][error]))) {
this.showError(error);
} else {
this.showError('decryptionError', { details: error.message || String(error) });
}
} finally {
this.hideLoading();
}
},
// --- Cryptographic Implementations ---
// AES
aesEncrypt(text, password) {
if (!password) throw 'invalidKeyError'; // Use translation key
return CryptoJS.AES.encrypt(text, password).toString();
},
aesDecrypt(ciphertext, password) {
if (!password) throw 'invalidKeyError'; // Use translation key
const bytes = CryptoJS.AES.decrypt(ciphertext, password);
const originalText = bytes.toString(CryptoJS.enc.Utf8);
if (!originalText) throw 'decryptionError'; // Use translation key
return originalText;
},
// RSA
generateRsaKeyPair() {
this.showLoading();
try {
const keySize = this.config.rsaKeySize;
const keypair = forge.pki.rsa.generateKeyPair({ bits: keySize, e: 0x10001 });
this.elements.rsaPublicKeyInput.value = forge.pki.publicKeyToPem(keypair.publicKey);
this.elements.rsaPrivateKeyInput.value = forge.pki.privateKeyToPem(keypair.privateKey);
this.showError('keyGenerationSuccess');
} catch (e) {
console.error(e);
this.showError('rsaKeyGenerationError');
} finally {
this.hideLoading();
}
},
rsaEncrypt(text, publicKeyPem) {
if (!publicKeyPem) throw 'missingPublicKey'; // Use translation key
try {
const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
const encrypted = publicKey.encrypt(forge.util.encodeUtf8(text), 'RSA-OAEP', {
md: forge.md.sha256.create(),
mgf1: { md: forge.md.sha1.create() }
});
return forge.util.encode64(encrypted);
} catch (e) {
console.error(e);
throw 'encryptionError'; // Use translation key
}
},
rsaDecrypt(ciphertextBase64, privateKeyPem) {
if (!privateKeyPem) throw 'missingPrivateKey'; // Use translation key
try {
const privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
const encrypted = forge.util.decode64(ciphertextBase64);
const decrypted = privateKey.decrypt(encrypted, 'RSA-OAEP', {
md: forge.md.sha256.create(),
mgf1: { md: forge.md.sha1.create() }
});
return forge.util.decodeUtf8(decrypted);
} catch (e) {
console.error(e);
throw 'decryptionError'; // Use translation key
}
},
// Hill Cipher
getHillMatrix() {
const size = this.config.hillMatrixSize;
const inputs = Array.from(this.elements.hillMatrixInputContainer.querySelectorAll('input'));
if (inputs.length !== size * size) throw 'hillMatrixInvalidFormat';
const matrix = [];
for (let i = 0; i < size; i++) {
const row = [];
for (let j = 0; j < size; j++) {
const val = parseInt(inputs[i * size + j].value);
if (isNaN(val) || val < 0 || val > 25) throw 'hillMatrixInvalidFormat';
row.push(val);
}
matrix.push(row);
}
const det = this.math.matrixDeterminant(matrix);
const invDet = this.math.modInverse(det, 26);
if (invDet === null) throw 'hillMatrixNotInvertible';
return matrix;
},
hillProcessText(text, keyMatrix, isEncrypt) {
const N = keyMatrix.length;
// Hill cipher traditionally works on a fixed alphabet, often uppercase English letters.
// This implementation will strip non-A-Z characters and convert to uppercase.
const cleanedText = text.toUpperCase().replace(/[^A-Z]/g, '');
if (!cleanedText && text.length > 0) { // If original text had chars but all were stripped
throw 'hillInputInvalidChars'; // Input had non-alphabetic chars only
}
if (cleanedText.length === 0 && text.length === 0) { // Truly empty input
return ""; // Return empty for empty valid input
}
let paddedText = cleanedText;
// Pad text if necessary
while (paddedText.length % N !== 0) {
paddedText += 'X'; // Pad with 'X'
}
let result = '';
const matrixToUse = isEncrypt ? keyMatrix : this.math.matrixInverse(keyMatrix, 26);
if (!matrixToUse) throw 'hillMatrixNotInvertible';
for (let i = 0; i < paddedText.length; i += N) {
const block = [];
for (let j = 0; j < N; j++) {
block.push(paddedText.charCodeAt(i + j) - 'A'.charCodeAt(0));
}
const resultBlock = this.math.multiplyMatrixVector(matrixToUse, block, 26);
for (let j = 0; j < N; j++) {
result += String.fromCharCode(resultBlock[j] + 'A'.charCodeAt(0));
}
}
return result;
},
hillEncrypt(text, keyMatrix) {
return this.hillProcessText(text, keyMatrix, true);
},
hillDecrypt(text, keyMatrix) {
// For Hill cipher decryption, input should also be A-Z
const cleanedText = text.toUpperCase().replace(/[^A-Z]/g, '');
if (!cleanedText && text.length > 0) {
throw 'hillInputInvalidChars';
}
if (cleanedText.length === 0 && text.length === 0) {
return "";
}
return this.hillProcessText(cleanedText, keyMatrix, false);
},
// XSalsa20-Poly1305
generateXsalsa20Key() {
const key = nacl.randomBytes(nacl.secretbox.keyLength);
this.elements.xsalsa20KeyInput.value = this.util.bytesToBase64(key);
this.showError('keyGenerationSuccess');
},
xsalsa20Encrypt(text, keyBase64) {
let key;
if (keyBase64) {
try { key = this.util.base64ToBytes(keyBase64); }
catch (e) { throw 'invalidBase64'; }
if (key.length !== nacl.secretbox.keyLength) throw 'invalidKeyError';
} else {
key = nacl.randomBytes(nacl.secretbox.keyLength);
this.elements.xsalsa20KeyInput.value = this.util.bytesToBase64(key);
}
const nonce = nacl.randomBytes(nacl.secretbox.nonceLength);
const messageUint8 = this.util.stringToBytes(text);
const box = nacl.secretbox(messageUint8, nonce, key);
const fullMessage = new Uint8Array(nonce.length + box.length);
fullMessage.set(nonce);
fullMessage.set(box, nonce.length);
return this.util.bytesToBase64(fullMessage);
},
xsalsa20Decrypt(ciphertextBase64, keyBase64) {
if (!keyBase64) throw 'invalidKeyError';
let key;
try { key = this.util.base64ToBytes(keyBase64); }
catch (e) { throw 'invalidBase64'; }
if (key.length !== nacl.secretbox.keyLength) throw 'invalidKeyError';
let fullMessage;
try { fullMessage = this.util.base64ToBytes(ciphertextBase64); }
catch (e) { throw 'invalidBase64'; }
if (fullMessage.length < nacl.secretbox.nonceLength) throw 'decryptionError'; // Ciphertext too short
const nonce = fullMessage.slice(0, nacl.secretbox.nonceLength);
const box = fullMessage.slice(nacl.secretbox.nonceLength);
const decryptedMessage = nacl.secretbox.open(box, nonce, key);
if (!decryptedMessage) throw 'decryptionError';
return this.util.bytesToString(decryptedMessage);
},
// ECIES (using eccrypto-js)
async generateEciesKeyPair() {
this.showLoading();
try {
const privateKey = eccrypto.generatePrivate();
const publicKey = eccrypto.getPublic(privateKey);
this.elements.eciesPrivateKeyInput.value = privateKey.toString('hex');
this.elements.eciesPublicKeyInput.value = publicKey.toString('hex');
this.showError('keyGenerationSuccess');
} catch (e) {
console.error(e);
this.showError('eciesKeyGenerationError');
} finally {
this.hideLoading();
}
},
async eciesEncrypt(text, publicKeyHex) {
if (!publicKeyHex) throw 'missingPublicKey';
try {
const publicKeyBuffer = Buffer.from(publicKeyHex, 'hex');
const msgBuffer = Buffer.from(text, 'utf8');
const encryptedStruct = await eccrypto.encrypt(publicKeyBuffer, msgBuffer);
const serialized = JSON.stringify({
iv: encryptedStruct.iv.toString('hex'),
ephemPublicKey: encryptedStruct.ephemPublicKey.toString('hex'),
ciphertext: encryptedStruct.ciphertext.toString('hex'),
mac: encryptedStruct.mac.toString('hex')
});
return btoa(serialized);
} catch (e) {
console.error("ECIES Encryption Error:", e);
throw 'encryptionError';
}
},
async eciesDecrypt(ciphertextBase64, privateKeyHex) {
if (!privateKeyHex) throw 'missingPrivateKey';
try {
const privateKeyBuffer = Buffer.from(privateKeyHex, 'hex');
const serialized = atob(ciphertextBase64);
const encryptedStructJSON = JSON.parse(serialized);
const encryptedStruct = {
iv: Buffer.from(encryptedStructJSON.iv, 'hex'),
ephemPublicKey: Buffer.from(encryptedStructJSON.ephemPublicKey, 'hex'),
ciphertext: Buffer.from(encryptedStructJSON.ciphertext, 'hex'),
mac: Buffer.from(encryptedStructJSON.mac, 'hex')
};
const decryptedBuffer = await eccrypto.decrypt(privateKeyBuffer, encryptedStruct);
return decryptedBuffer.toString('utf8');
} catch (e) {
console.error("ECIES Decryption Error:", e);
throw 'decryptionError';
}
},
// --- Math Helpers (for Hill Cipher) ---
math: {
extendedGcd(a, b) {
if (a === 0) return [b, 0, 1];
const [gcd, x1, y1] = this.extendedGcd(b % a, a);
const x = y1 - Math.floor(b / a) * x1;
const y = x1;
return [gcd, x, y];
},
modInverse(a, m) {
let ma = (a % m + m) % m; // Ensure a is positive and within modulo
const [gcd, x] = this.extendedGcd(ma, m);
if (gcd !== 1) return null;
return (x % m + m) % m;
},
matrixDeterminant(matrix) {
const N = matrix.length;
if (N === 1) return matrix[0][0];
if (N === 2) return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
if (N === 3) {
return matrix[0][0] * (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1]) -
matrix[0][1] * (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0]) +
matrix[0][2] * (matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0]);
}
throw new Error("Determinant for N > 3 not implemented.");
},
matrixInverse(matrix, mod) {
const N = matrix.length;
let det = this.matrixDeterminant(matrix);
det = (det % mod + mod) % mod; // Ensure determinant is positive in modulo
const invDet = this.modInverse(det, mod);
if (invDet === null) return null;
const adj = [];
if (N === 2) {
adj.push([(matrix[1][1] * invDet % mod + mod)%mod, (-matrix[0][1] * invDet % mod + mod)%mod]);
adj.push([(-matrix[1][0] * invDet % mod + mod)%mod, (matrix[0][0] * invDet % mod + mod)%mod]);
} else if (N === 3) {
for (let i = 0; i < 3; i++) {
adj[i] = [];
for (let j = 0; j < 3; j++) {
const M_ji = [ [], [] ];
let r_sub = 0;
for (let row = 0; row < 3; row++) {
if (row === j) continue;
let c_sub = 0;
for (let col = 0; col < 3; col++) {
if (col === i) continue;
M_ji[r_sub][c_sub] = matrix[row][col];
c_sub++;
}
r_sub++;
}
let cofactor_ji = ((j + i) % 2 === 0 ? 1 : -1) * this.matrixDeterminant(M_ji);
cofactor_ji = (cofactor_ji % mod + mod) % mod; // Ensure cofactor is positive in modulo
adj[i][j] = (cofactor_ji * invDet % mod + mod) % mod;
}
}
} else {
throw new Error("Inverse for N > 3 not implemented.");
}
return adj;
},
multiplyMatrixVector(matrix, vector, mod) {
const N = matrix.length;
const result = [];
for (let i = 0; i < N; i++) {
let sum = 0;
for (let j = 0; j < N; j++) {
sum += matrix[i][j] * vector[j];
}
result.push((sum % mod + mod) % mod);
}
return result;
}
},
// --- Utility Functions ---
util: {
stringToBytes(str) {
return new TextEncoder().encode(str);
},
bytesToString(bytes) {
return new TextDecoder().decode(bytes);
},
bytesToBase64(bytes) { // Accepts Uint8Array
let binary = '';
const len = bytes.byteLength;
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary);
},
base64ToBytes(base64) { // Returns Uint8Array
const binary_string = atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes;
}
},
// --- Matrix Background Effect ---
initMatrixBackground() {
const canvas = document.getElementById('matrixCanvas');
if (!canvas) return; // Exit if canvas not found
const ctx = canvas.getContext('2d');
const matrixBgDiv = document.querySelector('.matrix-bg');
if (!matrixBgDiv) return; // Exit if container not found
let width = matrixBgDiv.clientWidth;
let height = matrixBgDiv.clientHeight;
canvas.width = width;
canvas.height = height;
const symbols = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 titanTITAN泰坦加密器タイタン暗号化ツール";
const fontSize = 12;
let columns = Math.floor(width / fontSize);
const drops = [];
function resetDrops() {
drops.length = 0; // Clear array
columns = Math.floor(width / fontSize);
if (columns <=0) columns = 1; // Ensure at least one column
for (let x = 0; x < columns; x++) {
drops[x] = 1 + Math.random() * (height / fontSize); // Randomize starting position
}
}
resetDrops();
let frameCount = 0;
let animationFrameId;
function drawMatrix() {
frameCount++;
// Control animation speed (draw every Nth frame)
// Adjust the modulo value: lower for faster, higher for slower.
if (frameCount % 4 !== 0) {
animationFrameId = requestAnimationFrame(drawMatrix);
return;
}
ctx.fillStyle = "rgba(10, 15, 26, 0.06)"; // Fading effect
ctx.fillRect(0, 0, width, height);
ctx.fillStyle = "rgba(125, 249, 255, 0.6)"; // Cyan symbols, slightly more visible
ctx.font = fontSize + "px monospace";
for (let i = 0; i < drops.length; i++) {
if(drops[i] * fontSize > height && Math.random() > 0.975) { // Reset drop
drops[i] = 0;
}
const text = symbols[Math.floor(Math.random() * symbols.length)];
ctx.fillText(text, i * fontSize, drops[i] * fontSize);
drops[i]++;
}
animationFrameId = requestAnimationFrame(drawMatrix);
}
// Stop previous animation frame before starting a new one (e.g. on resize)
if(animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
drawMatrix();
window.addEventListener('resize', () => {
if(animationFrameId) {
cancelAnimationFrame(animationFrameId);
}
width = matrixBgDiv.clientWidth;
height = matrixBgDiv.clientHeight;
canvas.width = width;
canvas.height = height;
resetDrops();
drawMatrix(); // Restart drawing
});
}
};
// --- Initialize the App ---
document.addEventListener('DOMContentLoaded', () => {
App.init();
});
Also see: Tab Triggers