Cryptonoob 尝试在 .NET 上加密并在 Javascript 上解密
Cryptonoob tries to encrypt on .NET and decrypt on Javascript
对于特定的应用程序,我需要在我的 .NET 服务器上对称加密并在浏览器中解密。
我通常可以自由选择算法,所以我尝试了 AES-GCM,因为它在 .NET 上具有更好的内置 API,并且也受 crypto.subtle 支持。
虽然我没有让它工作,但我对从 crypto.subtle.decrypt
的调用中得到一个无用的异常感到难过,它不包含关于 Chrome 的消息并说“操作失败出于特定操作的原因”在 Firefox 上。
解密密码为(也here in codesandbox):
import "./styles.css";
import { Base64 } from "js-base64";
let nonce = Base64.toUint8Array("o/YcD/yZVU2egcGd");
async function importKey() {
const keyData = Base64.toUint8Array("3NraMtQP10qKGL3HLloObA==");
const key = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "AES-GCM" },
true,
["decrypt", "encrypt"]
);
return key;
}
var cypherText = Base64.toUint8Array("Is+l7cojlfbuU3vUN0gWMw==");
async function decrypt() {
const key = await importKey();
try {
return await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: nonce },
key,
cypherText
);
} catch (ex) {
console.error("Error: " + ex.message);
}
}
async function work() {
const decrypted = await decrypt();
const result = new TextDecoder().decode(decrypted);
document.getElementById("app").innerText = result;
}
work();
不完全确定 .NET 调用的 nonce 是不是 JS 调用的 iv。
在任何情况下,总是会到达 catch 处理程序。
为了比较,生成密文的 .NET 代码是(也是 here as a LINQPad query):
AesGcm.NonceByteSizes.Dump();
AesGcm.TagByteSizes.Dump();
var key = Guid.Parse("32dadadc-0fd4-4ad7-8a18-bdc72e5a0e6c")
.ToByteArray()
.ToArray();
var nonce = Guid.Parse("0f1cf6a3-99fc-4d55-9e81-c19d09003e9b")
.ToByteArray()
.Take(12)
.ToArray();
Convert.ToBase64String(key).Dump("key");
var aes = new AesGcm(key);
Convert.ToBase64String(nonce).Dump("nonce");
var text = Encoding.UTF8.GetBytes("Hello, world 123");
text.Length.Dump("cypher text size");
var buffer = new Byte[text.Length];
var tag = new Byte[16];
aes.Encrypt(nonce, text, buffer, tag, null);
String.Join(" ", from b in buffer select b.ToString("d")).Dump("cypher text");
Convert.ToBase64String(buffer).Dump("cypher text");
var text2 = new Byte[text.Length];
aes.Decrypt(nonce, buffer, tag, text2, null);
Encoding.UTF8.GetString(text2).Dump("check");
在.NET代码中,密文和标签是分开处理的,而在JavaScript代码中,两者必须连在一起处理:ciphertext | tag
.
在 .NET 代码中生成的身份验证标记根本没有应用到 JavaScript 代码中,它单独阻止了解密。
此外,我无法用 .NET 代码重现 JavaScript 代码中使用的密文。然而,密钥和随机数是可以复制的。当我 运行 .NET 代码时,我得到以下数据(Base64 编码):
nonce: o/YcD/yZVU2egcGd
key: 3NraMtQP10qKGL3HLloObA==
ciphertext: 1dupqLQFLXe31Pq48udCFw==
tag: kfMFJS+cy4VoDuFX1t7Reg==
如果JavaScript代码中使用了正确的密文,并且将密文和tag拼接,则解密成功:
// Concatenate ciphertext and tag!
const ciphertext = Base64.toUint8Array("1dupqLQFLXe31Pq48udCFw==");
const tag = Base64.toUint8Array("kfMFJS+cy4VoDuFX1t7Reg==");
const ciphertextTag = new Uint8Array(ciphertext.length + tag.length);
ciphertextTag.set(ciphertext);
ciphertextTag.set(tag, ciphertext.length);
let nonce = Base64.toUint8Array("o/YcD/yZVU2egcGd");
async function importKey() {
const keyData = Base64.toUint8Array("3NraMtQP10qKGL3HLloObA==");
const key = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "AES-GCM" },
true,
["decrypt", "encrypt"]
);
return key;
}
async function decrypt() {
const key = await importKey();
try {
return await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: nonce },
key,
ciphertextTag // Use the concatenated data!
);
} catch (ex) {
console.error("Error: " + ex.message);
}
}
async function work() {
const decrypted = await decrypt();
const result = new TextDecoder().decode(decrypted);
console.log(result);
}
work();
<script src="https://cdn.jsdelivr.net/npm/js-base64@3.2.4/base64.min.js"></script>
请注意,出于安全原因,密钥/随机数对只能使用一次(参见 GCM / Security)。通常为每次加密创建一个新的随机随机数。由于 nonce 不是秘密的,它通常放在密文之前:nonce | ciphertext | tag
。这被发送给接收者,接收者将随机数分开(并取决于 API,标签),因此拥有解密所需的所有信息。
对于特定的应用程序,我需要在我的 .NET 服务器上对称加密并在浏览器中解密。
我通常可以自由选择算法,所以我尝试了 AES-GCM,因为它在 .NET 上具有更好的内置 API,并且也受 crypto.subtle 支持。
虽然我没有让它工作,但我对从 crypto.subtle.decrypt
的调用中得到一个无用的异常感到难过,它不包含关于 Chrome 的消息并说“操作失败出于特定操作的原因”在 Firefox 上。
解密密码为(也here in codesandbox):
import "./styles.css";
import { Base64 } from "js-base64";
let nonce = Base64.toUint8Array("o/YcD/yZVU2egcGd");
async function importKey() {
const keyData = Base64.toUint8Array("3NraMtQP10qKGL3HLloObA==");
const key = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "AES-GCM" },
true,
["decrypt", "encrypt"]
);
return key;
}
var cypherText = Base64.toUint8Array("Is+l7cojlfbuU3vUN0gWMw==");
async function decrypt() {
const key = await importKey();
try {
return await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: nonce },
key,
cypherText
);
} catch (ex) {
console.error("Error: " + ex.message);
}
}
async function work() {
const decrypted = await decrypt();
const result = new TextDecoder().decode(decrypted);
document.getElementById("app").innerText = result;
}
work();
不完全确定 .NET 调用的 nonce 是不是 JS 调用的 iv。
在任何情况下,总是会到达 catch 处理程序。
为了比较,生成密文的 .NET 代码是(也是 here as a LINQPad query):
AesGcm.NonceByteSizes.Dump();
AesGcm.TagByteSizes.Dump();
var key = Guid.Parse("32dadadc-0fd4-4ad7-8a18-bdc72e5a0e6c")
.ToByteArray()
.ToArray();
var nonce = Guid.Parse("0f1cf6a3-99fc-4d55-9e81-c19d09003e9b")
.ToByteArray()
.Take(12)
.ToArray();
Convert.ToBase64String(key).Dump("key");
var aes = new AesGcm(key);
Convert.ToBase64String(nonce).Dump("nonce");
var text = Encoding.UTF8.GetBytes("Hello, world 123");
text.Length.Dump("cypher text size");
var buffer = new Byte[text.Length];
var tag = new Byte[16];
aes.Encrypt(nonce, text, buffer, tag, null);
String.Join(" ", from b in buffer select b.ToString("d")).Dump("cypher text");
Convert.ToBase64String(buffer).Dump("cypher text");
var text2 = new Byte[text.Length];
aes.Decrypt(nonce, buffer, tag, text2, null);
Encoding.UTF8.GetString(text2).Dump("check");
在.NET代码中,密文和标签是分开处理的,而在JavaScript代码中,两者必须连在一起处理:ciphertext | tag
.
在 .NET 代码中生成的身份验证标记根本没有应用到 JavaScript 代码中,它单独阻止了解密。
此外,我无法用 .NET 代码重现 JavaScript 代码中使用的密文。然而,密钥和随机数是可以复制的。当我 运行 .NET 代码时,我得到以下数据(Base64 编码):
nonce: o/YcD/yZVU2egcGd
key: 3NraMtQP10qKGL3HLloObA==
ciphertext: 1dupqLQFLXe31Pq48udCFw==
tag: kfMFJS+cy4VoDuFX1t7Reg==
如果JavaScript代码中使用了正确的密文,并且将密文和tag拼接,则解密成功:
// Concatenate ciphertext and tag!
const ciphertext = Base64.toUint8Array("1dupqLQFLXe31Pq48udCFw==");
const tag = Base64.toUint8Array("kfMFJS+cy4VoDuFX1t7Reg==");
const ciphertextTag = new Uint8Array(ciphertext.length + tag.length);
ciphertextTag.set(ciphertext);
ciphertextTag.set(tag, ciphertext.length);
let nonce = Base64.toUint8Array("o/YcD/yZVU2egcGd");
async function importKey() {
const keyData = Base64.toUint8Array("3NraMtQP10qKGL3HLloObA==");
const key = await crypto.subtle.importKey(
"raw",
keyData,
{ name: "AES-GCM" },
true,
["decrypt", "encrypt"]
);
return key;
}
async function decrypt() {
const key = await importKey();
try {
return await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: nonce },
key,
ciphertextTag // Use the concatenated data!
);
} catch (ex) {
console.error("Error: " + ex.message);
}
}
async function work() {
const decrypted = await decrypt();
const result = new TextDecoder().decode(decrypted);
console.log(result);
}
work();
<script src="https://cdn.jsdelivr.net/npm/js-base64@3.2.4/base64.min.js"></script>
请注意,出于安全原因,密钥/随机数对只能使用一次(参见 GCM / Security)。通常为每次加密创建一个新的随机随机数。由于 nonce 不是秘密的,它通常放在密文之前:nonce | ciphertext | tag
。这被发送给接收者,接收者将随机数分开(并取决于 API,标签),因此拥有解密所需的所有信息。