I'm trying to decrypt payload using SymmetricKey. I've tried ChaChaPoly and AES.GCM to open sealedBox but I'm still getting CryptoKit.CryptoKitError.authenticationFailure
here is my implementation:
let iv: [UInt8] = [0x00, 0x01, 0x02, 0x03,
0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B,
0x0C, 0x0D, 0x0E, 0x0F]
func generatePair() {
let priv = P256.KeyAgreement.PrivateKey()
privateKey = priv
publicKey = priv.publicKey
}
func createSymmetricKey(serverPublicKeyPEM: String) -> SymmetricKey? {
guard let privateKey = privateKey,
let publicKey = publicKey else { return nil }
do {
let serverPubKey = try P256.KeyAgreement.PublicKey(pemRepresentation: serverPublicKeyPEM)
let shared = try privateKey.sharedSecretFromKeyAgreement(with: serverPubKey)
let symetricKey = shared.hkdfDerivedSymmetricKey(using: SHA256.self,
salt: Data(bytes: iv, count: iv.count),
sharedInfo: publicKey.rawRepresentation + serverPubKey.rawRepresentation,
outputByteCount: 32)
return symetricKey
} catch {
//TODO: Handle Error
print("error \(error)")
return nil
}
}
func decrypt(payload: String, symmetricKey: SymmetricKey) {
guard let cipherText = Data(base64Encoded: payload) else { return }
do {
// let sealedBox = try ChaChaPoly.SealedBox(combined: cipherText)
// let decrypted = try ChaChaPoly.open(sealedBox, using: symmetricKey)
let sb = try AES.GCM.SealedBox(combined: cipherText)
let decrypted = try AES.GCM.open(sb, using: symmetricKey)
print("")
} catch {
print("error: \(error)") //here getting CryptoKit.CryptoKitError.authenticationFailure
}
}
Also I know how implementation on backend side looks like:
public static String encrypt(String sessionKey, String devicePublicKey, String plainString) throws Exception {
byte[] plain = Base64.getEncoder().encodeToString(plainString.getBytes(StandardCharsets.UTF_8)).getBytes();
SecretKey key = generateSharedSecret(decodePrivateKey(sessionKey), decodePublicKey( devicePublicKey));
Cipher encryptor = Cipher.getInstance("AES/CTR/NoPadding", BouncyCastleProvider.PROVIDER_NAME);
IvParameterSpec ivSpec = new IvParameterSpec(INITIALIZATION_VECTOR);
encryptor.init(Cipher.ENCRYPT_MODE, key, ivSpec);
return Base64.getEncoder().encodeToString(encryptor.doFinal(plain, 0, plain.length));
}
The issue probably lies within the initialisation vector or nonce you use. Counting the bytes, we come to a total of 16 nonce bytes, even though GCM only needs 12. Now, using 16 is not necessarily good or bad, but the CryptoKit implementation assumes 12 bytes when calling AES.GCM.SealedBox(combined:). In order to support 16 nonce bytes, you will have to use AES.GCM.SealedBox(nonce:ciphertext:tag:) instead.
let ciphertext = Data(...)
do {
let nonce = try AES.GCM.Nonce(data: ciphertext[0 ..< 16]
let message = ciphertext[16 ..< ciphertext.count - 16]
let tag = ciphertext[ciphertext.count - 16 ..< ciphertext.count]
let sb = try AES.GCM.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag)
let decrypted = try AES.GCM.open(sb, using: key)
} catch {
print("Error: \(error)")
}
Looking at your server code, make sure the shared secret is not 'just' the shared secret. generateSharedSecret sounds like it's the secret after performing key exchange, but without performing the key derivation (HKDF, as seen in the Swift code).
Also looking deeper into your server code, make sure your response data contains the nonce, encrypted message and tag. Some crypto implementations force you to do this concatenation yourself. So instead of return Base64(doFinal) (pseudo code), you should instead make sure doFinal includes a tag (GCM only), and return Base64(nonce + encrypted message + tag). Again, the tag only when using GCM.
As also mentioned in the comments, GCM and CTR are different modes of operation for AES. Make sure you use the same one on both parties, so either GCM on both iOS and server or CTR on both iOS and server. Not doing so, will always result in failure of decryption.
If you want to use CTR, you'll have to take a look at CommonCrypto, the old Apple crypto library. This one has CTR implemented, but doesn't support GCM (as the implementation was never released).
One final note, when using GCM, also make sure your additional authentication data (if any) is correct.
Related
I'm trying to get the equivalent of the following C# method :
public byte[] SignHash(byte[] btHash, string SN)
{
string strSignature = string.Empty;
X509Store x509store = null;
x509store = new X509Store(StoreLocation.CurrentUser);
x509store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 x509 in x509store.Certificates)
{
if (x509.SerialNumber.ToLower().Contains(SN.ToLower()))
{
byte[] btSignature = null;
using (RSACryptoServiceProvider key = new RSACryptoServiceProvider())
{
key.FromXmlString(x509.PrivateKey.ToXmlString(true));
return key.SignHash(btHash, CryptoConfig.MapNameToOID("SHA256"));
}
break;
}
}
return null;
}
In Java language. Actually, I've come to this :
private static String SignHash(final byte[] btHash, String SerialNumber) throws Exception
{
KeyStore ks = null;
ks = KeyStore.getInstance("Windows-MY");
ks.load(null, null);
Boolean noValidCertificate = true;
Enumeration<String> en = ks.aliases();
ArrayList<String> lstAlias = Collections.list(en);
lstErreurs.add(lstAlias.size() + " certificate(s) found");
for (String aliasKey : lstAlias)
{
X509Certificate cert = (X509Certificate) ks.getCertificate(aliasKey);
Certificat = Base64Coder.encodeBase64String(cert.getEncoded());
Boolean blnCertificateFound = false;
if (SerialNumber != null && !SerialNumber.equals(""))
{
String SerialNumberCert = cert.getSerialNumber().toString(16);
if (SerialNumber.toLowerCase().contains(SerialNumberCert.toLowerCase())
|| SerialNumberCert.toLowerCase().contains(SerialNumber.toLowerCase()))
{
blnCertificateFound = true;
}
}
if (blnCertificateFound == false)
{
continue;
}
Provider p = ks.getProvider();
boolean isHashToSign = false;
for (String strToSign : input.split(";")) {
if(strToSign.length() == 44 && General.isBase64(strToSign)) {
isHashToSign = true;
break;
}
}
String algorithm = "";
if(isHashToSign)
{
algorithm = "RSA";
} else {
algorithm = "SHA256withRSA";
}
Signature sig = Signature.getInstance(algorithm, p);
PrivateKey key = (PrivateKey) ks.getKey(aliasKey, "1234".toCharArray());
if (key != null)
{
noValidCertificate = false;
sig.initSign(key);
String[] TabToSign = input.split(";");
String strResultSignature = "";
String separator = "";
for (String strToSign : TabToSign)
{
byte[] btToSign = null;
if(isHashToSign) {
btToSign = General.Base64_Decode_To_ByteArray(strToSign.getBytes(Charset.forName("UTF-8")));
} else {
btToSign = strToSign.getBytes(Charset.forName("UTF-8"));
}
sig.update(btToSign);
byte[] res = sig.sign();
String resB64 = Base64Coder.encodeBase64String(res);
strResultSignature += separator + resB64;
separator = ";";
}
return strResultSignature;
}
}
return null;
}
But getting the algorithm "RSA" does not work for signature. I finally sign the Hash of a Hash in Java. I would like to sign a SHA256 byte array hash without hashing it again. How can I come to this result ? (for information, I'm using the Windows Certificate Store, so I have to work with Sun MSCAPI provider).
EDIT 1 :
I tried with the algorithm "NONEwithRSA" but the signature result is different from the signature in .NET using SignHash method.
EDIT 2 :
The following thread : Difference between SHA256withRSA and SHA256 then RSA explains it is actually possible to sign a hash, but that method requires BouncyCastle.
I can not operate with BouncyCastle because I need to use sun MSCAPI provider (Windows Certificate Store). I have to find an alternative to BouncyCastle (unless BC allows us to use the Sun MSCAPI provider).
(The answer was completely rewritten. Some less interesting thoughts and snippets can be found in the previous revisions)
A call to SignHash(btHash, CryptoConfig.MapNameToOID("SHA256")) does a PKCS#1 v1.5 signature (RSASSA-PKCS1-v1_5), e.g.:
byte[] btHash = new byte[] { 0x57, 0x91, 0x16, 0xB6, 0x3E, 0x06, 0x58, 0x83, 0x24, 0x8C, 0x07, 0x16, 0xDA, 0x6A, 0x03, 0x4D, 0x23, 0x37, 0x0B, 0x32, 0x1C, 0xA0, 0x80, 0x08, 0x1F, 0x42, 0x03, 0x81, 0x8E, 0x54, 0x3A, 0xC6 };
X509Certificate2 cert = new X509Certificate2("dummy.p12", "1234", X509KeyStorageFlags.Exportable);
using (RSACryptoServiceProvider key = new RSACryptoServiceProvider())
{
key.FromXmlString(cert.PrivateKey.ToXmlString(true));
byte[] ret = key.SignHash(btHash, CryptoConfig.MapNameToOID("SHA256"));
}
Gives a signature:
0A5003E549C4E4310F720076A5A4D785B165C4FE352110F6CA9877EB9F364D0C40B0197110304D6F92E8BD40DFD38DB91F356601CDD2CD34129BC54492C2D7F371D431150288A95C21E47533F01A9FA4977439FF9594C703380BEDF49A47A7B060ECAC26AEB53B8732D93E18FAD3B2D5889B3311C1B0D4F9F6B318169BDEB143D771DEFB56BAFE49B2B59F172757D4273EF369AFCB32490EC954E17599BD66D4E3BDB345B860748DB0C3B5A272ECFA546E65F2D4C87870CC62D91680AB71DB52DE618C006356258A941E8F36A5DCC7A06BA6DACAC3DC35F482B168107B4D7DA6C19A56FEDC247232DD7210CA9DB7273AA9AE6A90A8A4DFEB64BA0FBC830AB922
Which contains a PKCS#1 v1.5 padded DigestInfo and hash (when decrypted using the public key):
0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF003031300D060960864801650304020105000420579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6
As you have only the hash (and not the data) to be signed you need to use the NONEwithRSA algorithm in java (which should perform a PKCS#1 v1.5 padded signature of the input data without any hashing) and generate the correct input DigestInfo with the hash OID manually. Like that (with the help of Apache Commons Lang)::
byte[] btHash = new byte[] { ....the same.... };
KeyStore keystore = KeyStore.getInstance("PKCS12");
keystore.load(new FileInputStream("dummy.p12"), "1234".toCharArray());
PrivateKey privKey = (PrivateKey)keystore.getKey("Dummy", "1234".toCharArray());
byte[] asn=ArrayUtils.addAll(new byte[] { (byte)0x30, (byte)0x31, (byte)0x30, (byte)0x0d, (byte)0x06, (byte)0x09, (byte)0x60, (byte)0x86, (byte)0x48, (byte)0x01, (byte)0x65, (byte)0x03, (byte)0x04, (byte)0x02, (byte)0x01, (byte)0x05, (byte)0x00, (byte)0x04, (byte)0x20}, btHash);
Signature signature = Signature.getInstance("NONEwithRSA");
signature.initSign(privKey);
signature.update(asn);
byte[] ret = signature.sign();
Which gives the same signature as the C# code (using the default SunJCE/SunRsaSign providers).
The SunMSCAPI provider supports the NONEWithRSA algorithm with a restriction. Citing sun.security.mscapi.RSASignature javadoc:
NOTE: NONEwithRSA must be supplied with a pre-computed message digest.
Only the following digest algorithms are supported: MD5, SHA-1,
SHA-256, SHA-384, SHA-512 and a special-purpose digest algorithm which
is a concatenation of SHA-1 and MD5 digests.
Which at first sight might work for this scenario. Unfortunately:
Signature mscapiSignatureNoneWithRSA = Signature.getInstance("NONEwithRSA", "SunMSCAPI");
mscapiSignatureNoneWithRSA.initSign(mscapiPrivKey);
mscapiSignatureNoneWithRSA.update(btHash);
byte[] mscapiSignatureNoneWithRSA_btHash = mscapiSignatureNoneWithRSA.sign();
Gives a different signature:
CE26A9F84A85037856D8F910141CE7F68D6CAAB416E5C2D48ACD9677BBACCB46B41500452A79018A22AB1CA866DD878A76B040A343C1BABCDB683AFA8CE1A6CCCA48E3120521E8A7E4F8B62B453565E6A6DC08273084C0748C337724A84929630DC79E2EB1F45F5EEBA2148EC0CA5178F2A232A2AE8A5D22BB06C508659051CD1F5A36951B60F6322C5AEB5D4461FABE4D84E28766501A1583EC2A5D8553C163EC8DB9E80EF972233516BEC50AAC38E54C8B5E3B3DEAE90F37A1D230CD976ABEEE97A4601461E842F0F548DA7A74645AAB42586044B084E6A9F3EFE409EE12D98EB0B16CDC2C0AB75BF5DC9B52EBB7E3303C53A2B1BDF14000B99112B1871A6A
Which contains only a PKCS#1 v1.5 padded value of the hash (there is no ASN.1 DigestInfo sequence /which is wrong in this case/):
0001FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6
Trying to sign the DigestInfo from the SunJCE example gives an exception:
java.security.SignatureException: Message digest is too long
(Here and here are the reasons for this behavior.)
An alternative way to generate the signature using RSA private key encryption which gives the same signature as the C# code with SunJCE provider (using the same asn variable as above):
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, privKey);
byte[] ret = cipher.doFinal(asn);
Does not work with the SunMSCAPI provider:
cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", "SunMSCAPI");
cipher.init(Cipher.ENCRYPT_MODE, mscapiPrivKey);
byte[] ret = cipher.doFinal(asn1);
As it gives:
4A540DFAD44EBDAE89BF5DD52121DA831E7C394E0586DC9EAEF949C466E649979E19DF81A44801EBD80B8946605B4EFCED53011A844A3A4E023136F0CDEAA57EAAB1EA1FA75400B3B2D5FAB3955BEB13A178AC03DED6AACA0571412B74BCE30E772082A368B58E94D8E20D8F2A116BA5B3881824A014281E9F04BD687C087ACF7164CAF7C74274BA356A671ADA2BB4142504DB2883AFEDA563C6E590BC962725D6697402AB24391409F50D7D16B8BF64A1C0224C379EF9C7B8E493BE889A70674C3AEEC524366DBF9DE36FEE01F186FC00DE2F06096C46CC873D37E194CB217FBFCCF450C1F96C804022E25E1589DF67247927AAD59C66294B027DD5EE991D46
Which decrypted using the public key gives a nonsense:
3A9E0F985D1CF2CFDB45F201A68EF0F241ADBAED2D945FD36451CB4BE77D9B30D977004F95E6EDC208805E62870CD19D87C5E7F4E4C1AC2546F7F9C410299C9D203C47C2B547BAA55DA05C44DACB7E07C3F0DB99AE291E48A67EE089F8DA34EB1AABE352A7F94B082CFB167C0FE90761B79FCE238A0F3D0D917CA51220EEA9A43877703FC06CDC1F13A77CA9904E3660F7AD84DE0C34C877B737C20B1A117E60D69E6766054A30B95E6E88CF2C11AEE5CE30F2DD780C0334BE085302E73E0E5BB096893B7155E7A48CA16DD5EA9FC6F63090F7223E7FBAAA133BDFDA7251F412E395F4D8A462569BC5DCC34C2DF7D996BB3278C3F6B0E1EE9E729925937C8BAE
But (more interestingly) contains a valid PKCS#1 v1.5 padded encrypted plaintext when decrypted with the private key:
000211F7946FAD6BDB18830F8DD1238FD7EFCCFF041D55B88FBABDAAA6B06A5E9FD7556EB33678D9954D26E07B2FCE6D7304386DBDFC352C9932E2BA1794A3A0E0F6D78AA656DEB36CC483171A77ABF34408F4BF60661ECA79852B8E39C1A710976208FFBF6BE0DFB566149E6C5838762316F394B70BDF6D494F8C43C42CB6E527292DEF9204712CB24AC82C572BBC0E70A298D5FB050A27B54AFFA1332EEF37A14E65D379968BCE717BEC37C67A180DE943AAF2FE83560D33BC588E11B85D1C3391CCB13E4A80F57166BAC9003031300D060960864801650304020105000420579116B63E065883248C0716DA6A034D23370B321CA080081F4203818E543AC6
Which means that although given a private key for the encryption operation the SunMSCAPI uses the public key part (I did not dig into the implementation details to find the reason for this behavior).
So (AFAIK) the SunMSCAPI provider can not be directly used in your scenario...
(Please note that you will get a different result for each encryption run as the PKCS#1 v1.5 encryption padding contains a random data)
Fortunately there are some alternative choices:
[A] Abuse the SunMSCAPI internal API to perform the signature like that (again with the help of Apache Commons Lang):
// Obtain the handles
long hCryptKey = (Long)MethodUtils.invokeMethod(mscapiPrivKey, "getHCryptKey");
long hCryptProvider = (Long)MethodUtils.invokeMethod(mscapiPrivKey, "getHCryptProvider");
// Call the internal native method
Class<?> internalClass = Class.forName("sun.security.mscapi.RSASignature");
Method internalSignHashMethod = internalClass.getDeclaredMethod("signHash", boolean.class, byte[].class, int.class, String.class, long.class, long.class);
internalSignHashMethod.setAccessible(true);
byte[] res = (byte[])internalSignHashMethod.invoke(internalClass, false, btHash, btHash.length, "SHA-256", hCryptProvider, hCryptKey);
ArrayUtils.reverse(res); // Make it big endian
Which gives the same result as the C# code.
But is strongly dependand on the underlying SunMSCAPI implementation which can change at any moment
[B] Use JNI/JNA and call the winapi functions directly
Which is a cleaner approach as it depends on a public API
I have found this project, but have not given it a try
Good luck!
Appendix: RSA Private key used in the examples:
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDoZFvkEpdzXwSw
9g6cDxg9n/khCjLIO7E8VQFzu80C0iR0C6K05SHvTFEdssmzZmdCQi092ReSJRPH
yAOQUnlcMuCpi0m62ufZ4yNkZX5sH3fjHkP1FMv5CPtJOIArGFCMS4CufXu2XkXh
dbJuCLPJsUuiRsaoRg0Q6a8QVqWAR1oyVojTNFqzZWTLD46lQQIvINOrIeYvKklU
FUNcmq8PyArwEvxaDeiop4gVyizx7n7v213FjAXMfEG920O4DlnKjObdi1+PhejT
1RUxRUipTmAI2d3JmACpYH6+Il8Ck61wmKQ9IjoTstNeRfKGEkxH9RKP2P4ko5w9
8YfToVDXAgMBAAECggEAI5vNIMNghYMXuu3ZCzyc4ER07gUcBuZun+n+kPdD0JzW
jRmDUuiRLJOrEjvlACI+zD5LpGBxZilcQI57TU/13JTHK/N11rXYNODC+Y07s+GW
gyyOCS2om34u0udfbDsLjJO9If+ER0tmtcdNEeMveUY7aqAhrIMfWWoVMxGzxlXd
0kHWl4blnisjc01xCG4WcXVItyA6F8WZ3kL+BTeR5/3IwM72r9k7fcBkJZPLJZff
oZ2W+whGa9UXAkt6DQ6PlWMvt+AVcu84f8k/4FRRUNbY1OslO7zHbEc1K5qibpjb
6Tlfg2G+bux/1oCJ59bdyRP7FQMmgjLx49H17mwcgQKBgQD1j4HRtwSPnS+INPS4
nVpfEtb+wXGeDLCMAgdesgXYfr5JWJKJCKW65/j2TkJ/xoN8L1z8TeE6o6Q3ZHJ9
QtcM1ifCpNCnjjalbkm9BG4bCPy+5MUIuS5oRtJjwb7mPTxzpq4DIj3G2ooY5F2i
9Nyqde3bEvWn910yqQgI6CjOtwKBgQDyRYkU46gKLhp98gJ0zdm3KNZ/TBE5zsO6
rDnwFPLGxanVNMzkIQX/LXAQOaNK1WD8fmKG+iixDVLyJYtVtuvTQLOHkOOTXw44
QY4BGh+wbS0HrfKd7Qcpt/3HTCKq9eW33+jggyBc+fa+LDIGpdbO16SBCP3Cb7k6
9gtBN5du4QKBgQCKriVO3uGAifESJ3Yd3R/wmZ85+N3FuLzsFSk8XaXXgpzMp2z6
XxvZ1rBPyhrcNqyDMex9wS32A/z2G5BdFaaF5VxHHPWJ61MJUqPqT9ovAoBa/rAY
IR0IXxbqp7y8ItFFL1kPBAOHjlx3emE3arpEup0+IBMEbTsBJV0YSqThOQKBgFRf
syX7QwKIm+FQ71oOdsw7BLjAnR8syy2v3V2nbgWbwVHnWZP5jEUaZfTAngXp2iUV
PusTJCjFIyYBvUzUr7yaw+tqolcou6ML8ZCgsHiZDR2njt9BNUVqNo+6DDjN+nrX
GBtYj2TSCQSiD6oRB4Zxw3DM2NNmZXQLTFAiNDMBAoGBAJOu4+nVB8iUE3JBsrM5
WkwBivqTyo9pusxwEs+GHnkVFsFubFKHda04220JMRVevOuf48DPgvlW6UCTojyr
85dFon9tV0qyi9Ehc0OdXqOjmx0y+wdR6ZqG+x+e6JGiYeReIa4XBrq0cLHlzrNY
8UwL0QLJpuaQZEbqhyOGMNKE
-----END PRIVATE KEY-----
As I am new to encryption and particularly in Java/Android, I am struggling to find tutorials and code that work fine so that I can learn from them but the results.
As in this site: https://www.owasp.org/index.php/Using_the_Java_Cryptographic_Extensions
I couldn't find the problem that hit me with BASE64Encoder class, which seems to be inside package a sun.utils but I can find Base64 class but I could not tweak code so that it could work for me.
Similarly in this
android encryption/decryption with AES
The encryption is done in Bitmap Image I could not realize the same technique in normal text string.
Would someone supply a simple AES encryption/decryption example in Android just showing how to use key,message, encryption and decryption?
I have used this for my project.
'com.scottyab:aescrypt:0.0.1'(ref: [enter link description here][1]
encryption:
String encryptedMsg = AESCrypt.encrypt(password, message);
decryption:
String messageAfterDecrypt = AESCrypt.decrypt(password, encryptedMsg);
if you are more concerned about security go with SHA1 or SHA256.
Hope this helps.
Edit:
In my case, i have to encrypt login password and send it to server over the network.
String userPassword = password.getText().toString();
try {
encryptedMsg = AESCrypt.encrypt(userPassword, Config.secretlogin);
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
So in backend, i have decrypted the secure password with the key and in both sides i am using the same AES(Android and backend).
I couldn't find the problem that hit me with BASE64Encoder class,
which seems to be inside package a sun.utils but i can find Base64
class but i could not tweak code so that it could work for me.
You might not be using the right library for Base64. You mention sun.utils, where the link you sent is using:
import org.apache.commons.codec.binary.Base64;
Since Java 8, you can use java.util.Base64, as detailed on Oracle documentation here. It supports Basic encoding, URL encoding and MIME encoding. You can find some examples in this tutorial.
The second example should work for text strings. Replace
byte[] b = baos.toByteArray();
with
byte[] b = yourString.getBytes();
Note that you have to store the key in some way because at some point have to be deciphered
Stored on the device is not a good idea because you're leaving the key to the door. You can store on your server or use a passphrase (fixed or asked the user). I give you a real sample of the last option
private static String getPassphraseSize16(String key) {
if (TextUtils.isEmpty(key)) {
return null;
}
char controlChar = '\u0014';
String key16 = key + controlChar;
if (key16.length() < 16) {
while (key16.length() < 16) {
key16 += key + controlChar;
}
}
if (key16.length() > 16) {
key16 = key16.substring(key16.length() - 16, key16.length());
}
return key16;
}
public static byte[] encodeAES(byte[] message, String passphrase) {
String passphrase16 = getPassphraseSize16(passphrase);
SecretKeySpec secretKey = new SecretKeySpec(passphrase16.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
byte[] encodedText = cipher.doFinal(message);
return encodedText;
}
public static byte[] decodeAES(byte[] encodedMessage, String key) {
String passphrase16 = getPassphraseSize16(key);
SecretKeySpec secretKey = new SecretKeySpec(passphrase16.getBytes(), "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decodedText = cipher.doFinal(encodedMessage);
return decodedText;
}
This example is similar to provided in https://github.com/scottyab/AESCrypt-Android
The encryption and decryption process worked fine after:
I replaced the byte array obtained from :
Bitmap
by byte array of message string as instructed in
android encryption/decryption with AES
So even though my problem is not fully solved, this question should be marked as answered.
I am having some troubles with the following code - I seem to be getting an IllegalBlocksizeException and am unsure what it is that I maybe doing incorrectly here? Would it be possible to get some advice / pointers?
Thanks
public class Encryption
{
private SecretKeyFactory factory;
private SecretKey tmp;
private SecretKey secret;
private Cipher cipher;
private byte[] iv;
private byte[] cipherText;
private final KeySpec spec = new PBEKeySpec("somepassword".toCharArray(), SALT, 65536, 256);
private static final byte[] SALT = {(byte)0xc3, (byte)0x23, (byte)0x71, (byte)0x1c, (byte)0x2e, (byte)0xc2, (byte)0xee, (byte)0x77};
public Encryption()
{
try
{
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
tmp = factory.generateSecret(spec);
secret = new SecretKeySpec(tmp.getEncoded(), "AES");
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public String encrypt(String valueToEncrypt) throws Exception
{
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(iv));
cipherText = cipher.doFinal(Base64.decodeBase64(valueToEncrypt.getBytes()));
return Base64.encodeBase64String(cipherText);
}
public String decrypt(String encryptedValueToDecrypt) throws Exception
{
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
return new String(cipher.doFinal(new Base64().encode(encryptedValueToDecrypt.getBytes())));
}
public static void main(String[] args ) throws Exception
{
Encryption manager = new Encryption();
String encrypted = manager.encrypt("this is a string which i would like to encrypt");
System.out.println(encrypted);
String decrypted = manager.decrypt(encrypted);
System.out.println(decrypted);
System.out.println(encrypted.equals(decrypted));
}
}
The exception is as follows
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:750)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at encrypt.Encryption.decrypt(Encryption.java:52)
at encrypt.Encryption.main(Encryption.java:60)
The only approach to begin with the implementation of cryptographic algorithms, from the functional point of view (please keep in mind that a working code is not necessarily a secure one, and a lot of thought should come in that direction), is incremental: first try raw AES with a fixed key, then add the key generated by PBKDF2 and only later Base64. The latter is just an encoding tool and should be the easiest part of the process.
But let's take a look to the code:
1. The initialization seems fine, if your goal is to generate the key out of a password.
2. During the decryption, this line stands off:
cipherText = cipher.doFinal(Base64.decodeBase64(valueToEncrypt.getBytes()));
valueToEncrypt is a readable string, but you're trying to decrypt it. Since it has only lowercase letters and spaces, it might not trigger an error, but you're trying to base64-decode something that hasn't been base64-encoded. It would make more sense to try:
cipherText = cipher.doFinal(valueToEncrypt.getBytes());
Then the cipherText can be base64-encoded.
For the decryption part, undo the operations in encryption in the reverse order. If you encrypted and then base64-encoded, then base64-decode first and then decrypt.
As a final recommendation: think modular. Encode in one line and encrypt in another, so if you want to remove or add a layer you just have to toggle the comments on one line.
You have reversed the base-64 encoding and decoding operations. Base-64 takes raw bytes and makes them into printable text. You can encode the output of an encryption operation to make it printable. But then you will need to base‑64–decode that text before trying to decrypt it.
This part of your decrypt() method is causing the problem:
cipher.doFinal(new Base64().encode(encryptedValueToDecrypt.getBytes()))
That should be:
cipher.doFinal(Base64.decodeBase64(encryptedValueToDecrypt.getBytes()))
Asking for "pointers" is pretty open-ended. The best pointer I can give you: don't write this code yourself. Choose a package that provides a higher-level API, selecting high-security algorithms and applying them according to best practices. You don't know what you are doing, and you won't be able to write secure code. But using a high quality, open source library might help you begin to learn more about encryption.
You probably should base64 decode encryptedValueToDecrypt before you decrypt it.
I try to decrypt an encrypted data that I receive from a web service.
The encryption is done using AES 128.
I use the following code to decrypt the data:
public static String decrypt(String strToDecrypt)
{
try
{
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); //AES/CBC/PKCS7Padding
SecretKeySpec secretKey = new SecretKeySpec(AppConstants.AESEncryptionKey.getBytes("UTF8"), "AES");
int blockSize = cipher.getBlockSize();
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[blockSize])); //new IvParameterSpec(new byte[16])
byte decBytes[] = cipher.doFinal(Base64.decode(strToDecrypt, 0));
// byte decBytes[] = cipher.doFinal(Base64.decodeBase64(strToDecrypt));
String decStr = new String(decBytes);
System.out.println("After decryption :" + decStr);
return decStr;
}
catch (Exception e)
{
System.out.println("Exception in decryption : " + e.getMessage());
}
return null;
}
At
cipher.doFinal()
I got the following Exception:
javax.crypto.badpaddingexception pad block corrupted
I went through my post but ended up with no solution. I am badly stuck over here.
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG","Crypto");
works perfectly
Note: This code works only on devices up to Android 6. Starting with Android 7.0 the "Crypto" provider has been removed, therefore this code will fail.
AES keys should consist of random data. If you store them as a String then you are likely to loose information, especially if you use encodings such as UTF-8. Your line:
AppConstants.AESEncryptionKey.getBytes("UTF8")
Makes it likely that you've lost data during conversion to/from a string. Use hexadecimals instead if you require a string, or simply store the key as a byte array.
Note that this answer doesn't indicate any security related hints. In general you only want to derive keys or store them in containers. You don't want to use CBC over an insecure channel either.
In my case issue is came because encrypted key and decrypted key both are different, when I check both key with same value then issue is not came
I need to encrypt / decrypt a username field and I was planning to use the code below:
public class Decrypter {
Cipher dcipher;
byte[] salt = new String("12345678").getBytes();
int iterationCount = 1024;
int keyStrength = 256;
SecretKey key;
byte[] iv;
Decrypter(String passPhrase) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), salt, iterationCount, keyStrength);
SecretKey tmp = factory.generateSecret(spec);
key = new SecretKeySpec(tmp.getEncoded(), "AES");
dcipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
}
public String encrypt(String data) throws Exception {
dcipher.init(Cipher.ENCRYPT_MODE, key);
AlgorithmParameters params = dcipher.getParameters();
iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] utf8EncryptedData = dcipher.doFinal(data.getBytes());
String base64EncryptedData = new sun.misc.BASE64Encoder().encodeBuffer(utf8EncryptedData);
System.out.println("IV " + new sun.misc.BASE64Encoder().encodeBuffer(iv));
System.out.println("Encrypted Data " + base64EncryptedData);
return base64EncryptedData;
}
public String decrypt(String base64EncryptedData) throws Exception {
dcipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] decryptedData = new sun.misc.BASE64Decoder().decodeBuffer(base64EncryptedData);
byte[] utf8 = dcipher.doFinal(decryptedData);
return new String(utf8, "UTF8");
}
public static void main(String args[]) throws Exception {
Decrypter decrypter = new Decrypter("ABCDEFGHIJKL");
String encrypted = decrypter.encrypt("StringToBeEncrypted");
String decrypted = decrypter.decrypt(encrypted);
System.out.println(decrypted);
}
}
I've taken this code from another site. The above code works fine when run as standalone. But the issue that I'm facing is how to decrypt the value when username is already encrypted?
I'll be calling encrypt & decrypt functions from different classes, so if the string is already encrypted & stored in the DB, then when user logs into website, when I'll call decrypt method, how do I pass the IV as CBC mode decrypt requires an IV parameter, while I've not stored iv during the encryption???
Any help is much appreciated!!
NOTE: This has nothing to do with password protection. As mentioned, need to encrypt userid & not password! For password protection, I'm using hash only.
The IV is something you need to supply when encrypting or decrypting data.
Like salt for a hash, the IV ensures that the identical plaintexts will never result in indentical ciphertexts.
You need to generate a (securely) random IV when you encrypt each plaintext and store it alongside the ciphertext.
To decrypt you have to have the IV and the secret key.
Though it is less secure, what I've seen is that people always keep the key (or password) safe somewhere and sometimes just code the IV into the program.
byte[] iv = new byte[]
{
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f
};
Or with some other set of byte values.
[Note that your code is generating an IV for each call to encrypt()]
Edit
Commenter #SLaks points out that using a constant IV reduces the level of protection and removes the extra level of security added by using CBC (which uses the IV). It reduces to the level of ECB (which has no IV).
(Note: in the code above:
Cipher.getInstance("AES/CBC/PKCS5Padding");
where CBC is selected.)
This is significant in that a particular string of bytes will encrypt to the same result every time when the key, IV and salt used are the same. IV is there to make this stop happening.
We want the encrypted results to be as random looking as possible. That keeps the bad guys from figuring out the original content.
Think of the IV as adding randomness to the plain-text message. For example, you might be encrypting passwords people give you. Those people tend to choose poor passwords and multiple people tend to choose the same one. Adding randomness would be a good thing in this case.
Think of a salt as adding randomness to the passphrase (which is just a fancy word for password to highlight using a long and varied one). In this case, again, people choose poor ones and adding randomness to them makes the encrypted results more random.
That's why you would choose a random bunch of bits to serve as the IV for each message encrypted. To keep it from looking like other encrypted messages. But they have to be stored with each message so it can be decrypted.
Any choosing a random bunch of bits to serve as the salt for each person will serve to make their messages encrypt and look different from anyone elses messages. You could use a different salt each time the person logs in or each time they change their password or even just once per person. However you do it, you have to save the salt values so you can decrypt the messages later.
If you need this level of security, be sure to generate a truly random IV for each message encrypted and store it somewhere to be used when decrypting.