I need some help with successfully implementing 3des encryption on my flutter project, heres what i have and i dont know where i could be going wrong.
Following the documentation on https://pub.flutter-io.cn/packages/flutter_3des I get
java.security.InvalidException: Wrong Key Size.
void doEncryption() async {
String key = "Seaxveqnxpqt0q2w";
String string = "3056775474;011";
var _encrypt = await Flutter3des.encrypt(string, key, iv: iv)
}
Here's a screenshot of my implementation.
Related
Since Flutter doesn't support any map APIs across all platforms (mobile and desktop), I'm trying to fetch map snapshots with Apple's Web Snapshots API. This involves constructing a URL with various options then signing the URL. I append the signature to the end of my request URL so Apple can verify that it's from me.
Apple's instructions state:
To generate a signature, sign the string with your private key using a ES256 algorithm (also known as ECDSA using P-256 curve and SHA-256 hash algorithm). The signature must be Base64 URL-encoded.
I don't need to decrypt anything, I just need to sign the string and add it to the end of my request URL. So I don't think I need anything beyond the crypto library included with Flutter.
Here's what I've tried:
import 'package:crypto/crypto.dart';
//Private Key
var key = utf8.encode('''
-----BEGIN PRIVATE KEY-----
abcdef...
-----END PRIVATE KEY-----
''');
var bytes = utf8.encode('My URL String to Sign...');
var hmacSha256 = Hmac(sha256, key);
var sig = hmacSha256.convert(bytes);
var signature = base64UrlEncode(sig.bytes);
I get an unintelligible string as signature and add it to my request URL, but I still get a 401 Not Authorized error, so my signature must be incorrect.
How can I properly sign my URL string with my private key?
Using pointycastle, you need a suitable random number generator instance and a signer initialized with the relevant digest. Then just call generateSignature. That only gets you the r and s values which you need to encode.
Here's an example:
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/asn1.dart';
import 'package:pointycastle/export.dart';
// the private key
ECPrivateKey? privateKey;
// some bytes to sign
final bytes = Uint8List(0);
// a suitable random number generator - create it just once and reuse
final rand = Random.secure();
final fortunaPrng = FortunaRandom()
..seed(KeyParameter(Uint8List.fromList(List<int>.generate(
32,
(_) => rand.nextInt(256),
))));
// the ECDSA signer using SHA-256
final signer = ECDSASigner(SHA256Digest())
..init(
true,
ParametersWithRandom(
PrivateKeyParameter(privateKey!),
fortunaPrng,
),
);
// sign the bytes
final ecSignature = signer.generateSignature(bytes) as ECSignature;
// encode the two signature values in a common format
// hopefully this is what the server expects
final encoded = ASN1Sequence(elements: [
ASN1Integer(ecSignature.r),
ASN1Integer(ecSignature.s),
]).encode();
// and finally base 64 encode it
final signature = base64UrlEncode(encoded);
A huge thanks to Richard Heap for providing the solution. I just wanted to post the final code I settled on for anyone running into this in the future. Only the basic_utils package is needed.
import 'package:basic_utils/basic_utils.dart';
import 'dart:typed_data';
final url = 'My URL string...';
//Convert the URL string to Uint8List
final urlBytes = utf8.encode(url) as Uint8List;
//Prep the private key
var key = '''
-----BEGIN PRIVATE KEY-----
abcdef...
-----END PRIVATE KEY-----
''');
ECPrivateKey privateKey = CryptoUtils.ecPrivateKeyFromPem(key);
//Sign the URL
ECSignature sig = CryptoUtils.ecSign(privateKey, urlBytes, algorithmName: 'SHA-256/ECDSA');
//Convert signature to Base64
final signature = CryptoUtils.ecSignatureToBase64(sig);
I just add that signature to the end of the URL string as required by Apple's API and it works great!
How do I decrypt a message when I have only the message and the key?
I know the key (as a string), and the encrypted message is returned from my friend's API (as a string). My friend uses CryptoJS to encrypt with AES. How do I decrypt that message? I think my friend uses the default settings for everything (in CryptoJS).
I'm trying it with encrypt in flutter. The thing is encrypter.decrypt() accepts the type Encrypted and iv while I have the encrypted message as a String. How do I convert it to Encrypted? Also, how do I obtain the iv?
Here's an example of the known information (both are String)
Encrypted message: U2FsdGVkX1851cYw0S6LX/xhUwdy0R/1AlNun5L9Ykc=
Key Example: myKey111
I'm currently out of ideas.
Here's what I have currently
String key = 'myKey111';
String keyB64 = base64.encode(utf8.encode(key));
final keyKey = encrypt.Key.fromBase64(keyB64);
String code = "U2FsdGVkX1851cYw0S6LX/xhUwdy0R/1AlNun5L9Ykc=";
List<int> list = code.codeUnits;
Uint8List bytes = Uint8List.fromList(list);
final _encrypted = encrypt.Encrypted(bytes);
print('KEY TEST: ${keyKey.base64}');
final _encrypter = encrypt.Encrypter(encrypt.AES(
keyKey,
mode: encrypt.AESMode.cbc,
padding: 'PKCS7',
));
final iv = encrypt.IV.fromUtf8('myKey111');
final _decrypted = _encrypter.decrypt(_encrypted, iv: iv);
I'll explain my problem, I'm trying to encrypt a password, then save it locally so that I can go and retrieve it when necessary, decrypt it and assign it wherever I want.
In the encryption phase I do not get any problems in the sense that the encrypt method can easily return a string while in the decrypt method I have problems, because the return type seems to have to be of the Encrypt type.
By doing so it is impossible for me to go and get the string saved locally thanks to sharedPreferences and be able to decrypt it, I am going to give you the code below to better understand my problem:
class _State extends State<SettingsPage> {
static final key = encrypt.Key.fromLength(32);
static final iv = encrypt.IV.fromLength(16);
static final encrypter = encrypt.Encrypter(encrypt.AES(key));
static encryptAES(text) {
final encrypted = encrypter.encrypt(text, iv: iv);
return encrypted;
}
static decryptAES(text) {
print(text.base64);
String decrypted = encrypter.decrypt(text, iv: iv);
print(decrypted);
return decrypted;
}
SettingsPage.inputPassword = input[0];
// SettingsPage.inputPassword = tec.text;
encryptedText = encryptAES(SettingsPage.inputPassword).base64;
setState(() {
String encryptedText =
encryptAES(SettingsPage.inputPassword).base64;
print("PROVA ENCRYPTED TEXT " + encryptedText);
});
prefs.setString('savedPass', encryptedText.base64);
String decryptedText = decryptAES(encryptedText).base64;
print("PROVA TESTO DECRIPTATO " + decryptedText);
}
},
Everything proceeds as required until the password is saved, then I ask you how to decrypt a string in order to be able to assign the variable DecryptedText to the String variable correctly, since at this moment I am told:
Unhandled Exception: NoSuchMethodError: Class 'String' has no instance getter 'base64'.
This is because it would appear that the decrypted method can only return an Encrypt type.
The pub.dev API used is encrypt: ^5.0.1.
THANKS.
I'm not familiar with package:encrypt, but skimming over the documentation you should be using Encrypted.fromBase64 to construct an Encrypted object to pass to Encrypter.decrypt:
static String decryptAES(String base64Text) {
print(base64Text);
String decrypted = encrypter.decrypt(Encrypted.fromBase64(base64Text), iv: iv);
print(decrypted);
return decrypted;
}
Note that you also should be calling just decryptAES(...), not decryptAES(...).base64. (You want the original text back, not a base64-encoded version.)
encryptedTextBase64 = encryptAES(SettingsPage.inputPassword).base64;
...
String decryptedText = decryptAES(encryptedTextBase64);
I will point out that your code is very hard to follow (which is especially bad for security-related code) because:
Your functions do not declare argument types nor return types and therefore take and return dynamic types. This makes it hard to understand what arguments they expect and what they are expected to return.
You should name your variables to make it clear what is base64-encoded and what isn't. (I renamed the variables in the example above.)
Having created a signed message I'm unsure how to use the resulting signature to verify the message using the publicKey.
My use case is, I'm wanting to use a Solana Wallet to login to an API server with a pattern like:
GET message: String (from API server)
sign message with privateKey
POST signature (to API server)
verify signature with stored publicKey
I've attempted to use nodeJS crypto.verify to decode the signed message on the API side but am a bit out of my depth digging into Buffers and elliptic curves:
// Front-end code
const toHexString = (buffer: Buffer) =>
buffer.reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
const data = new TextEncoder().encode('message to verify');
const signed = await wallet.sign(data, "hex");
await setLogin({ // sends API post call to backend
variables: {
publicAddress: walletPublicKey,
signature: toHexString(signed.signature),
},
});
// Current WIP for backend code
const ALGORITHM = "ed25519";
const fromHexString = (hexString) =>
new Uint8Array(hexString.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
const signature = fromHexString(args.signature);
const nonceUint8 = new TextEncoder().encode('message to verify');
const verified = crypto.verify(
ALGORITHM,
nonceUint8,
`-----BEGIN PUBLIC KEY-----\n${user.publicAddress}\n-----END PUBLIC KEY-----`,
signature
);
console.log("isVerified: ", verified);
I'm pretty sure I'm going about this the wrong way and there must be an obvious method I'm missing.
As the space matures I expect a verify function or lib will appear to to consume the output of const signed = await wallet.sign(data, "hex");
Something like:
import { VerifyMessage } from '#solana/web3.js';
const verified = VerifyMessage(message, publicKey, signature, 'hex');
But after 3 days of pushing hard I'm starting to hit my limits and my brain is failing. Any help or direction where to look much appreciated 🙏
Solved with input from the fantastic Project Serum discord devs. High level solution is to use libs that are also used in the sol-wallet-adapter repo, namely tweetnacl and bs58:
const signatureUint8 = base58.decode(args.signature);
const nonceUint8 = new TextEncoder().encode(user?.nonce);
const pubKeyUint8 = base58.decode(user?.publicAddress);
nacl.sign.detached.verify(nonceUint8, signatureUint8, pubKeyUint8)
// true
I recommend staying in solana-labs trail and use tweetnacl
spl-token-wallet (sollet.io) signs an arbitrary message with
nacl.sign.detached(message, this.account.secretKey)
https://github.com/project-serum/spl-token-wallet/blob/9c9f1d48a589218ffe0f54b7d2f3fb29d84f7b78/src/utils/walletProvider/localStorage.js#L65-L67
on the other end, verify is done with
nacl.sign.detached.verify
in #solana/web3.js
https://github.com/solana-labs/solana/blob/master/web3.js/src/transaction.ts#L560
Use nacl.sign.detached.verify in your backend and you should be good. I also recommend avoiding any data format manipulation, I am not sure what you were trying to do but if you do verify that each step is correct.
For iOS, solana.request will cause error. Use solana.signMessage and base58 encode the signature.
var _signature = '';
try {
signedMessage = await window.solana.request({
method: "signMessage",
params: {
message: encodedMessage
},
});
_signature = signedMessage.signature;
} catch (e) {
try {
signedMessage = await window.solana.signMessage(encodedMessage);
_signature = base58.encode(signedMessage.signature);
} catch (e1) {
alert(e1.message);
}
}
//
try {
signIn('credentials',
{
publicKey: signedMessage.publicKey,
signature: _signature,
callbackUrl: `${window.location.origin}/`
}
)
} catch (e) {
alert(e.message);
}
I needed to convert Uint8Array to string and convert it back to Uint8Array for HTTP communication. I found the toLocaleString method of Uint8Array helpful in this case. It outputs comma-separated integers as a string.
const signedMessage = await window.solana.signMessage(encodedMessage, "utf8");
const signature = signedMessage.signature.toLocaleString();
And then you can convert it back to Uint8Array with the following code.
const signatureUint8 = new Uint8Array(signature.split(",").map(Number));
Edit
The solution above was working on the desktop but when I tried my code inside the Phantom wallet iOS browser it gave an error. I guess the toLocaleString method is not available in that browser. I found a more solid solution to convert Uint8Array to a comma-separated string
Array.apply([], signedMessage.signature).join(",")
Signing and base64 encode:
const data = new TextEncoder().encode(message);
const signature = await wallet.signMessage(data); // Uint8Array
const signatureBase64 = Buffer.from(signature).toString('base64')
Base64 decode and verifying:
const signatureUint8 = new Uint8Array(atob(signature).split('').map(c => c.charCodeAt(0)))
const messageUint8 = new TextEncoder().encode(message)
const pubKeyUint8 = wallet.publicKey.toBytes() // base58.decode(publicKeyAsString)
const result = nacl.sign.detached.verify(messageUint8, signatureUint8, pubKeyUint8) // true or false
Full code example: https://github.com/enginer/solana-message-sign-verify-example
Hello I'm trying to see if they're any JWT token library similar to JOSE-JWT that also works on a Linux machine during runtime.
This is what I currently have in code that decrypts a token and grabs what I need.
private IamTokenDecrypted DecryptToken(string idToken)
{
byte[] key = WebEncoders.Base64UrlDecode(_ssoEncryptionKeyInfo.JsonWebKey.K);
string json = Jose.JWT.Decode(idToken, key, JweAlgorithm.DIR, JweEncryption.A256GCM);
var result = Jose.JWT.Payload(json);
var iamTokenDecrypted = JsonConvert.DeserializeObject<IamTokenDecrypted>(result);
return iamTokenDecrypted;
}
I have a security key and and as you see it has some encryption in it