【问题标题】:AES Encryption : Encrypt using Arduino and decrypt using JavaAES 加密:使用 Arduino 加密并使用 Java 解密
【发布时间】:2016-12-26 16:17:59
【问题描述】:

我想使用 Arduino 加密文本并使用 Java 解密。我从this link 尝试了这段代码,但没有成功。

我正在使用这个Arduino library 对 Arduino 和 Java 端的 Java 加密扩展 (JCE) 框架进行加密。

这是 Arduino 代码:

#include <AESLib.h>  //replace the ( with < to compile (forum posting issue)
#include <Base64.h>

void setup() {
  Serial.begin(9600);
  uint8_t key[] = {50,50,50,50,50,50,50,50,50,50,50,50,50,50,50,50};
  //expressed in 16 unsigned in characters, be careful not to typecast this as a char in a decrypter
  //16- 50's (uint8) is the way to express 16 2's in ASCII, the encryption matches to what will show up on <a href="http://aesencryption.net/" target="_blank" rel="nofollow">http://aesencryption.net/</a>
  char data[] = "0123456789012345";
  //The message to encrypt, 16 chars == 16 bytes, no padding needed as frame is 16 bytes
  char encryptedData[100];
  int *size;
  Serial.print("Message:");
  Serial.println(data);
  aes128_enc_single(key, data);
  Serial.print("encrypted:");
  Serial.println(data);
  int inputLen = sizeof(data);
  int encodedLen = base64_enc_len(inputLen);
  char encoded[encodedLen];
  base64_encode(encoded, data, inputLen);
  Serial.print("encrypted(base64):"); //used
  Serial.println(encoded);
  Serial.println("***********Decrypter************");
  int input2Len = sizeof(encoded);
  int decodedLen = base64_dec_len(encoded, input2Len);
  char decoded[decodedLen];
  base64_decode(decoded, encoded, input2Len);
  Serial.print("encrypted (returned from Base64):");
  Serial.println(decoded);
  Serial.print("decrypted:");
  Serial.println(decoded);
}

void loop() {
}

这是Java代码:

package main;

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class ForTest {
    public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
        String message= "0123456789012345";//Message to encode  
        String key = "2222222222222222";  
        // 128 bit key  -this key is processed as ASCII values  
        System.out.println("Processing 3.0 AES-128 ECB Encryption/Decryption Example");
        System.out.println("++++++++++++++++++++++++++++++++");
        System.out.println("Original Message: " + message);
        System.out.println("Key: " + key);
        System.out.println("key in bytes: "+key.getBytes("UTF-8"));
        System.out.println("==========================");           
        //Encrypter
        SecretKeySpec skeySpec_encode = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
        Cipher cipher_encode  = Cipher.getInstance("AES/ECB/NoPadding");
        //          Cipher cipher_encode = Cipher.getInstance("AES/ECB/PKCS5PADDING"); //AES-CBC with IV encoding, ECB is used without the IV, example shown on <a href="http://aesencryption.net/" target="_blank" rel="nofollow">http://aesencryption.net/</a> 
        cipher_encode.init(Cipher.ENCRYPT_MODE, skeySpec_encode);
        byte[] encrypted = cipher_encode.doFinal(message.getBytes());
        System.out.println("Encrypted String (base 64): "
                + DatatypeConverter.printBase64Binary(encrypted));
        //encode without padding: Base64.getEncoder().withoutPadding().encodeToString(encrypted));
        //encode with padding:  Base64.getEncoder().encodeToString(encrypted));
        String base64_encrypted = DatatypeConverter.printBase64Binary(encrypted);
        //Decrypter
        SecretKeySpec skeySpec_decode = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
        Cipher cipher_decode  = Cipher.getInstance("AES/ECB/NoPadding");
        //          Cipher cipher_decode = Cipher.getInstance("AES/ECB/PKCS5PADDING");
        cipher_decode.init(Cipher.DECRYPT_MODE, skeySpec_decode);
        System.out.println("length: "+"Ouril+UTDF8htLzE".length());
        byte[] decrypted_original = cipher_decode.doFinal(DatatypeConverter.parseBase64Binary("Ouril+UTDF8htLzEhiRj7wA="));
        String decrypt_originalString = new String(decrypted_original);
        System.out.println("Decrypted String: " + decrypt_originalString);
    }
}

在 Java 中,当我尝试用 Arduino 解密编码的字符串时,我得到了这个:

Processing 3.0 AES-128 ECB Encryption/Decryption Example
++++++++++++++++++++++++++++++++
Original Message: 0123456789012345
Key: 2222222222222222
key in bytes: [B@2a139a55
==========================
Encrypted String (base 64): Ouril+UTDF8htLzEhiRj7w==
length: 16
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes
    at com.sun.crypto.provider.CipherCore.finalNoPadding(CipherCore.java:1016)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:960)
    at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
    at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
    at javax.crypto.Cipher.doFinal(Cipher.java:2165)
    at main.ForTest.main(ForTest.java:46)

有什么想法吗? 谢谢!

【问题讨论】:

  • 您尝试base64解码和解密的字符串是Ouril+UTDF8htLzEhiRj7wA=,在base64解码后是17个字节,后面有一个0字节。之前打印的字符串是Ouril+UTDF8htLzEhiRj7w==,除了尾随字节之外,它是相同的(在base 64解码之后),并且是正确的长度(16字节)。
  • 但是为什么我在使用 arduino 和 Java 进行 base64 编码后没有得到相同的结果?使用 Arduino 我得到了:Ouril+UTDF8htLzEhiRj7wA=,使用 Java 我得到了Ouril+UTDF8htLzEhiRj7w==,我该如何纠正呢?谢谢!
  • Arduino 输出引入了一个额外的字节。我不熟悉 Arduino 语言本身,但快速浏览一下,它是 C/C++ 的一个子集。 C以其字符串“空终止”而闻名(即,为了指示具有任意长度的字符串的结尾,它添加了一个包含值0x00的字节)。这可能就是这里发生的事情。
  • 在您的 Java 代码中,您需要将输入的密文修剪到 0x00 字节之前的字节。此外,如果输入的明文和密文不是 16 字节的精确倍数(AES 的块大小),并且ECB 是最差的密码块模式(相当于 在大多数情况下不加密)。查看cipher block modes of operation 上的此 Wikipedia 条目并选择一个更好的选项(GCM 建议用于 AEAD 属性)。
  • 您也可以从源头解决这个问题,并且只有 Base64 将密文的内容编码为 Arduino 中的空字节,但我不太了解这种语言是否有一个简单的这样做的方法。您可能只能在编码之前从char[] 中修剪最后一个字节。

标签: java encryption arduino cryptography aes


【解决方案1】:

大约一周后我就可以完成这项工作了——关于与其他系统集成的 Arduino 文档是废话:)

工作 Arduino 代码:

#include "mbedtls/aes.h"
#include <Arduino.h>
#include <HTTPClient.h>
#include <base64.h>

void makeUpdateAPICall()
{
  if (WiFi.status() == WL_CONNECTED)
  {
    HTTPClient http;

    // Your Domain name with URL path or IP address with path
    http.begin(serverName); 

    // Specify content-type header
    http.addHeader("Content-Type", "text/plain");
    http.addHeader("Authorization", "Bearer XXXXXXXX [whatever your web token is]");
    http.addHeader("X-Content-Type-Options", "nosniff");
    http.addHeader("X-XSS-Protection", "1; mode=block");

    //AES Encrypt
    esp_aes_context aesOutgoing;
    unsigned char key[32] = "1234567812345678123456781234567" ;
    key[31] = '8';   // we replace the 32th (index 31) which contains '/0' with the '8' char.

    char *input = "Tech tutorials x";
    unsigned char encryptOutput[16];

    mbedtls_aes_init(&aesOutgoing);
    mbedtls_aes_setkey_enc(&aesOutgoing, key, 256);
    int encryptAttempt = mbedtls_aes_crypt_ecb(&aesOutgoing, MBEDTLS_AES_ENCRYPT, (const unsigned char *)input, encryptOutput);
    USE_SERIAL.println();
    USE_SERIAL.println("MBEDTLS_AES_EBC encryption result:\t ");
    USE_SERIAL.print(encryptAttempt); //0 means that the encrypt/decrypt function was successful
    USE_SERIAL.println();
    mbedtls_aes_free(&aesOutgoing);

    int encryptSize = sizeof(encryptOutput) / sizeof(const unsigned char);
    USE_SERIAL.println("Size of AES encrypted output: ");
    USE_SERIAL.println(encryptSize);

    //Base 64 Encrypt
    int inputStringLength = sizeof(encryptOutput);
    int encodedLength = Base64.decodedLength((char *)encryptOutput, inputStringLength);
    char encodedCharArray[encodedLength];
    Base64.encode(encodedCharArray, (char *)encryptOutput, inputStringLength);
    //Send to server
    USE_SERIAL.print("Sending to server.");
    int httpResponseCode = http.POST(encodedCharArray);

    String payload = "{}";

    if (httpResponseCode > 0)
    {
      //Retrieve server response
      payload = http.getString();
    }
    // Free resources
    http.end();
  }
  WiFi.disconnect();
}

工作 Java 代码:

public static String decrypt(String strToDecrypt, String key) {

    byte[] encryptionKeyBytes = key.getBytes();  
    Cipher cipher;
    try {
        cipher = Cipher.getInstance("AES/ECB/NoPadding");
        SecretKey secretKey = new SecretKeySpec(encryptionKeyBytes, "AES");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);         
        return new String(cipher.doFinal(Base64.getDecoder().decode(strToDecrypt.getBytes("UTF-8"))));
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        e.printStackTrace();
    } catch (IllegalBlockSizeException e) {
        e.printStackTrace();
    } catch (BadPaddingException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}

现在正在处理退货流程。

你用这段代码调用 Java 端:

final String decryptedText = AES.decrypt(encryptedStr, "12345678123456781234567812345678"); System.out.println("Decrypted AES ECB String: "); System.out.println(decryptedText);

想为任何发现自己在同一条船上的可怜的懒汉提供这个:)

希望这会有所帮助!

【讨论】:

  • 这对于固定大小的加密输出没有多大用处,并且不需要所有将密钥作为字符串的恶作剧:如果您将其保留在 OP 中,那么尾随空问题不会出现。
  • @user207421 你说的很对。但是,我的示例的目的只是为 Arduino 和 Java 之间的 AES 集成提供一种可行的方法。那里真的没有文档(至少我能找到)。我的生产代码使用填充、AES CBC,并从服务器上的安全位置提取密钥。从安全位置提取密钥和其他东西更多的是 BAU,但诀窍是让解密/加密部分在来自其他系统/语言时工作。希望这可以帮助某人克服这个障碍。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-01-07
  • 2013-06-22
  • 2014-01-14
  • 2011-02-18
  • 2015-02-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多