Androidデータ暗号化のRsa暗号化


前言:
最近は同僚とデータの安全な伝送をする気がないので、自分が使っていたRsa非対称暗号化アルゴリズムを思い出して、暇になったらまとめてみます。 
他のいくつかの暗号化方式:
 •Androidデータ暗号化のRsa暗号化
 •Androidデータ暗号化のAes暗号化
 •Androidデータ暗号化のDes暗号化
 •Androidデータ暗号化のMD 5暗号化
 •Androidデータ暗号化のBase 64符号化アルゴリズム
 •Androidデータ暗号化SHAセキュリティハッシュアルゴリズム 
Rsa暗号とは何ですか?
RSAアルゴリズムは、最も人気のある公開鍵暗号アルゴリズムであり、長さを変化させる鍵を使用する。RSAは、データの暗号化にもデジタル署名にも使える最初のアルゴリズムである。
RSAアルゴリズムの原理は以下の通りです。
1.ランダムに2つの大きな素数pとqを選択し、pはqに等しくなく、N=pqを計算する。
2.1より大きい自然数eを選択し、eは(p-1)(q-1)と相互素養しなければなりません。
3.数式でd:dを計算します。×e=1(mod(p-1)(q-1)です。
4.pとqを廃棄する。
最終的に得られたNとeは「公開鍵」であり、dは「秘密鍵」であり、送信者はNを使ってデータを暗号化し、受信者はdを使ってのみデータの内容を解くことができる。
RSAの安全性は、大きな数の分解に依存しています。1024ビット未満のNは安全ではないことが証明されました。RSAアルゴリズムは大きな数の計算を行うので、RSAが最も速い場合もDESより倍遅くなります。これはRSAの最大の欠陥です。したがって、通常は少量のデータまたは暗号化鍵を暗号化するためにのみ使用されますが、RSAは依然として高い強度のアルゴリズムとなります。
どうやって使うべきですか  
第一歩:まず秘密鍵ペアを生成します。 

 /**
  *     RSA   
  *
  * @param keyLength     ,  :512~2048
  *       1024
  * @return
  */
 public static KeyPair generateRSAKeyPair(int keyLength) {
  try {
   KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
   kpg.initialize(keyLength);
   return kpg.genKeyPair();
  } catch (NoSuchAlgorithmException e) {
   e.printStackTrace();
   return null;
  }
 }

具体的な暗号化の実現: 
公開鍵の暗号化 

 /**
  *            
  *
  * @param data   
  */
 public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
  //     
  X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
  KeyFactory kf = KeyFactory.getInstance(RSA);
  PublicKey keyPublic = kf.generatePublic(keySpec);
  //     
  Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
  cp.init(Cipher.ENCRYPT_MODE, keyPublic);
  return cp.doFinal(data);
 }

秘密鍵の暗号化 

 /**
  *     
  *
  * @param data       
  * @param privateKey   
  * @return byte[]     
  */
 public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) throws Exception {
  //     
  PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
  KeyFactory kf = KeyFactory.getInstance(RSA);
  PrivateKey keyPrivate = kf.generatePrivate(keySpec);
  //     
  Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
  cipher.init(Cipher.ENCRYPT_MODE, keyPrivate);
  return cipher.doFinal(data);
 }

公開鍵の復号 

 /**
  *     
  *
  * @param data       
  * @param publicKey   
  * @return byte[]     
  */
 public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) throws Exception {
  //     
  X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
  KeyFactory kf = KeyFactory.getInstance(RSA);
  PublicKey keyPublic = kf.generatePublic(keySpec);
  //     
  Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
  cipher.init(Cipher.DECRYPT_MODE, keyPublic);
  return cipher.doFinal(data);
 }

秘密鍵の復号 

 /**
  *         
  */
 public static byte[] decryptByPrivateKey(byte[] encrypted, byte[] privateKey) throws Exception {
  //     
  PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
  KeyFactory kf = KeyFactory.getInstance(RSA);
  PrivateKey keyPrivate = kf.generatePrivate(keySpec);

  //     
  Cipher cp = Cipher.getInstance(ECB_PKCS1_PADDING);
  cp.init(Cipher.DECRYPT_MODE, keyPrivate);
  byte[] arr = cp.doFinal(encrypted);
  return arr;
 }

いくつかのグローバル変数の解説: 

 public static final String RSA = "RSA";//          
 public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";//      
 public static final int DEFAULT_KEY_SIZE = 2048;//      
 public static final byte[] DEFAULT_SPLIT = "#PART#".getBytes(); //          bufferSize,   partSplit      
 public static final int DEFAULT_BUFFERSIZE = (DEFAULT_KEY_SIZE / 8) - 11;//               

暗号化の充填方式について:以前は上のこれらの操作でRSaの復号が実現できると思っていましたが、万事大吉と思いました。ほほほ、これはまだ終わっていません。悲劇はやはり発生しました。Android側で暗号化されたデータはサーバー側では復号できません。androidシステムのRSAは実は「RSA/None/Nopadding」です。標準JDKは「RSA/None/PKCS 1 dding」です。これは、Androidマシンで暗号化されてサーバーで復号できない原因になりますので、実現時にはこれに注意してください。 
セグメント暗号化を実現する:充填方式を解決した後、自信を持って万事大吉と判断しましたが、意外にも発生しました。RSA非対称暗号化の内容長さに制限があり、1024ビットkeyの最大は127ビットのデータしか暗号化できません。そうでないとエラーが発生します。最近の使用では「不正な長さ」という異常があり、暗号化されているデータが長すぎていることが判明しました。RSAアルゴリズムは、暗号化されているバイト数は、鍵の長さ値を8で割ってから11を減算してはいけません。暗号化された後、暗号文のバイト数が得られ、ちょうど鍵の長さ値が8で除算されます。
公開鍵のセグメント暗号化 

/**
  *              
  *
  */
 public static byte[] encryptByPublicKeyForSpilt(byte[] data, byte[] publicKey) throws Exception {
  int dataLen = data.length;
  if (dataLen <= DEFAULT_BUFFERSIZE) {
   return encryptByPublicKey(data, publicKey);
  }
  List<Byte> allBytes = new ArrayList<Byte>(2048);
  int bufIndex = 0;
  int subDataLoop = 0;
  byte[] buf = new byte[DEFAULT_BUFFERSIZE];
  for (int i = 0; i < dataLen; i++) {
   buf[bufIndex] = data[i];
   if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
    subDataLoop++;
    if (subDataLoop != 1) {
     for (byte b : DEFAULT_SPLIT) {
      allBytes.add(b);
     }
    }
    byte[] encryptBytes = encryptByPublicKey(buf, publicKey);
    for (byte b : encryptBytes) {
     allBytes.add(b);
    }
    bufIndex = 0;
    if (i == dataLen - 1) {
     buf = null;
    } else {
     buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
    }
   }
  }
  byte[] bytes = new byte[allBytes.size()];
  {
   int i = 0;
   for (Byte b : allBytes) {
    bytes[i++] = b.byteValue();
   }
  }
  return bytes;
 }

秘密鍵のセグメント暗号化 

 /**
  *     
  *
  * @param data          
  * @param privateKey   
  */
 public static byte[] encryptByPrivateKeyForSpilt(byte[] data, byte[] privateKey) throws Exception {
  int dataLen = data.length;
  if (dataLen <= DEFAULT_BUFFERSIZE) {
   return encryptByPrivateKey(data, privateKey);
  }
  List<Byte> allBytes = new ArrayList<Byte>(2048);
  int bufIndex = 0;
  int subDataLoop = 0;
  byte[] buf = new byte[DEFAULT_BUFFERSIZE];
  for (int i = 0; i < dataLen; i++) {
   buf[bufIndex] = data[i];
   if (++bufIndex == DEFAULT_BUFFERSIZE || i == dataLen - 1) {
    subDataLoop++;
    if (subDataLoop != 1) {
     for (byte b : DEFAULT_SPLIT) {
      allBytes.add(b);
     }
    }
    byte[] encryptBytes = encryptByPrivateKey(buf, privateKey);
    for (byte b : encryptBytes) {
     allBytes.add(b);
    }
    bufIndex = 0;
    if (i == dataLen - 1) {
     buf = null;
    } else {
     buf = new byte[Math.min(DEFAULT_BUFFERSIZE, dataLen - i - 1)];
    }
   }
  }
  byte[] bytes = new byte[allBytes.size()];
  {
   int i = 0;
   for (Byte b : allBytes) {
    bytes[i++] = b.byteValue();
   }
  }
  return bytes;
 }

公開鍵のセグメント解読 

 /**
  *       
  *
  * @param encrypted      
  * @param publicKey   
  */
 public static byte[] decryptByPublicKeyForSpilt(byte[] encrypted, byte[] publicKey) throws Exception {
  int splitLen = DEFAULT_SPLIT.length;
  if (splitLen <= 0) {
   return decryptByPublicKey(encrypted, publicKey);
  }
  int dataLen = encrypted.length;
  List<Byte> allBytes = new ArrayList<Byte>(1024);
  int latestStartIndex = 0;
  for (int i = 0; i < dataLen; i++) {
   byte bt = encrypted[i];
   boolean isMatchSplit = false;
   if (i == dataLen - 1) {
    //  data    
    byte[] part = new byte[dataLen - latestStartIndex];
    System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
    byte[] decryptPart = decryptByPublicKey(part, publicKey);
    for (byte b : decryptPart) {
     allBytes.add(b);
    }
    latestStartIndex = i + splitLen;
    i = latestStartIndex - 1;
   } else if (bt == DEFAULT_SPLIT[0]) {
    //     split[0]  
    if (splitLen > 1) {
     if (i + splitLen < dataLen) {
      //     data   
      for (int j = 1; j < splitLen; j++) {
       if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
        break;
       }
       if (j == splitLen - 1) {
        //    split     ,   break,        split 
        isMatchSplit = true;
       }
      }
     }
    } else {
     // split    ,      
     isMatchSplit = true;
    }
   }
   if (isMatchSplit) {
    byte[] part = new byte[i - latestStartIndex];
    System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
    byte[] decryptPart = decryptByPublicKey(part, publicKey);
    for (byte b : decryptPart) {
     allBytes.add(b);
    }
    latestStartIndex = i + splitLen;
    i = latestStartIndex - 1;
   }
  }
  byte[] bytes = new byte[allBytes.size()];
  {
   int i = 0;
   for (Byte b : allBytes) {
    bytes[i++] = b.byteValue();
   }
  }
  return bytes;
 }

秘密鍵のセグメント解読 

 /**
  *         
  *
  */
 public static byte[] decryptByPrivateKeyForSpilt(byte[] encrypted, byte[] privateKey) throws Exception {
  int splitLen = DEFAULT_SPLIT.length;
  if (splitLen <= 0) {
   return decryptByPrivateKey(encrypted, privateKey);
  }
  int dataLen = encrypted.length;
  List<Byte> allBytes = new ArrayList<Byte>(1024);
  int latestStartIndex = 0;
  for (int i = 0; i < dataLen; i++) {
   byte bt = encrypted[i];
   boolean isMatchSplit = false;
   if (i == dataLen - 1) {
    //  data    
    byte[] part = new byte[dataLen - latestStartIndex];
    System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
    byte[] decryptPart = decryptByPrivateKey(part, privateKey);
    for (byte b : decryptPart) {
     allBytes.add(b);
    }
    latestStartIndex = i + splitLen;
    i = latestStartIndex - 1;
   } else if (bt == DEFAULT_SPLIT[0]) {
    //     split[0]  
    if (splitLen > 1) {
     if (i + splitLen < dataLen) {
      //     data   
      for (int j = 1; j < splitLen; j++) {
       if (DEFAULT_SPLIT[j] != encrypted[i + j]) {
        break;
       }
       if (j == splitLen - 1) {
        //    split     ,   break,        split 
        isMatchSplit = true;
       }
      }
     }
    } else {
     // split    ,      
     isMatchSplit = true;
    }
   }
   if (isMatchSplit) {
    byte[] part = new byte[i - latestStartIndex];
    System.arraycopy(encrypted, latestStartIndex, part, 0, part.length);
    byte[] decryptPart = decryptByPrivateKey(part, privateKey);
    for (byte b : decryptPart) {
     allBytes.add(b);
    }
    latestStartIndex = i + splitLen;
    i = latestStartIndex - 1;
   }
  }
  byte[] bytes = new byte[allBytes.size()];
  {
   int i = 0;
   for (Byte b : allBytes) {
    bytes[i++] = b.byteValue();
   }
  }
  return bytes;
 }

このようにしてようやく出会った問題を解決しました。プロジェクトで使っている案はクライアントの公開鍵を暗号化し、サーバの秘密鍵を復号します。サーバー開発者は効率的に考えていると言っています。やはり自分でプログラムを書いて、本当の効率をテストしてみました。 
第一歩:対象データを100個用意する 

  List<Person> personList=new ArrayList<>();
  int testMaxCount=100;//         
  //      
  for(int i=0;i<testMaxCount;i++){
   Person person =new Person();
   person.setAge(i);
   person.setName(String.valueOf(i));
   personList.add(person);
  }
  //FastJson  json  

  String jsonData=JsonUtils.objectToJsonForFastJson(personList);

  Log.e("MainActivity","   json   ---->"+jsonData);
  Log.e("MainActivity","   json     ---->"+jsonData.length());

ステップ2:秘密鍵ペアを生成する 

  KeyPair keyPair=RSAUtils.generateRSAKeyPair(RSAUtils.DEFAULT_KEY_SIZE);
  //   
  RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
  //   
  RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); 
次に公開鍵を使って秘密鍵を復号します。   公開鍵の暗号解読 

  //    
  long start=System.currentTimeMillis();
  byte[] encryptBytes= RSAUtils.encryptByPublicKeyForSpilt(jsonData.getBytes(),publicKey.getEncoded());
  long end=System.currentTimeMillis();
  Log.e("MainActivity","       cost time---->"+(end-start));
  String encryStr=Base64Encoder.encode(encryptBytes);
  Log.e("MainActivity","   json   --1-->"+encryStr);
  Log.e("MainActivity","   json     --1-->"+encryStr.length());
  //    
  start=System.currentTimeMillis();
  byte[] decryptBytes= RSAUtils.decryptByPrivateKeyForSpilt(Base64Decoder.decodeToBytes(encryStr),privateKey.getEncoded());
  String decryStr=new String(decryptBytes);
  end=System.currentTimeMillis();
  Log.e("MainActivity","       cost time---->"+(end-start));
  Log.e("MainActivity","   json   --1-->"+decryStr);

  //    
  start=System.currentTimeMillis();
  encryptBytes= RSAUtils.encryptByPrivateKeyForSpilt(jsonData.getBytes(),privateKey.getEncoded());
  end=System.currentTimeMillis();
  Log.e("MainActivity","        cost time---->"+(end-start));
  encryStr=Base64Encoder.encode(encryptBytes);
  Log.e("MainActivity","   json   --2-->"+encryStr);
  Log.e("MainActivity","   json     --2-->"+encryStr.length());
  //    
  start=System.currentTimeMillis();
  decryptBytes= RSAUtils.decryptByPublicKeyForSpilt(Base64Decoder.decodeToBytes(encryStr),publicKey.getEncoded());
  decryStr=new String(decryptBytes);
  end=System.currentTimeMillis();
  Log.e("MainActivity","       cost time---->"+(end-start));
  Log.e("MainActivity","   json   --2-->"+decryStr);

実行結果:

対照的に発見された:秘密鍵の復号化は時間がかかりますので、必要に応じて解決できない方式を採用して復号することができます。個人的には、サーバーが復号効率が高く、クライアントの秘密鍵が暗号化され、サーバの公開鍵が復号されるほうがいいと思います。 
暗号化後のデータサイズの変化:データ量はほぼ暗号化前の1.5倍です。

 以上が本文の全部です。皆さんの勉強に役に立つように、私たちを応援してください。