情報の機密性確保のために、内容を隠蔽することを暗号化(encryption)と呼びます。 そして、暗号化する前の文を平文と呼び、 暗号から平文に戻すことを復号と呼びます。 暗号化や復号の処理に使うデータを鍵と呼びます。
暗号化と復号で、どちらも同じ鍵を使う方式です。
換字方式、加算方式、転置方式などがあります。
以下に示すDES(Data Encryption Standard)の方式では、加算と転置を組み合わせています。
加算方式は、XORのビット演算を使います。
XORは、演算する2つのビットが同じであれば0、そうでなければ1にする演算です。
これを使うと、平文のビットを、鍵で1の位置だけ、反転させることができます。
得られた結果が暗号文です。これを同じキーを再び演算すると、もとの平文に戻せます。
←これで、その演算を検証ください。
参考:コード表
スイッチの画像をクリックしてビットを指定して計算します。
共通鍵暗号方式は、入力データを固定サイズ(ブロック長)に分割してから、各ブロックごとで暗号化する
ブロック暗号と、連続したビット列(1ビット〜数ビットまたは1byte)ずつ暗号化するストリーム暗号があります。
代表的な共通暗号方式を以下に示します。
名称 | 鍵サイズ | 方式 | 概要 |
---|---|---|---|
DES | 56bit | ブロック暗号 | 以前(77年)米国の標準暗号に採用 |
RC4 | 40〜256bit | ストリーム暗号 | IEEE801.11bの無線通信用のWEPで採用 |
A5/1,A5/2 | 64bit | ストリーム暗号 | GSM携帯電話のプライバシー保護用に採用 |
AES | 128,192,256bit | ブロック暗号 | DESの後継 |
Javaで作成したDES暗号キー作成のプログラムを、
ここよりダウンロードして実験できます。
(ソース:MakeKeyDES.java)
実行例を示します。以下では、suzuki_DESKey.txtの鍵ファイルを作成しています。
Z:\security>java MakeKeyDES 適当な入力から、共通鍵を作ります。作成されるファイル名:name_DESKey.txt 名前入力>suzuki (起動時間で乱数を作る場合は、単にEnterを入力ください) 乱数の種を数値で入力>12345678 作成した共通鍵 =549441917164084766010242426951(先頭8byteを使用) Bu9Xs5wx768= 06 EF 57 B3 9C 31 EF AF suzuki_DESKey.txtの鍵ファイル生成終了 Z:\security>
作成された鍵ファイル(suzuki_DESKey.txt)の内容を示します。
Bu9Xs5wx768=
なお、このコードはBase64と呼ばれる表記です。 (メールなどでよく使われるもので、バイナリーデータを効率よくテキストデータで表現するためのコードです。)
Javaで作成した暗号化プログラムを、
ここよりダウンロードして実験できます。
(ソース:EncryptDES.java)
実行例を示します。まず、次の平文を『平文.txt』で用意します。
6月29日に 隠れて、逢いましょう。
次のように実行します。まず、鍵を選択するダイアログが現れます。 それに対して『suzuki_DESKey.txt』のファイルを選んでいます。 次に上記で作成した『平文.txt』を選択しています。
Z:\security>java EncryptDES _DESKeyファイルを読み取ります。 読み取り内容:Bu9Xs5wx768=byte列 06 EF 57 B3 9C 31 EF AF 暗号化対象を読み取ります。 Z:\security\suzuki_DESKey.txtで、 Z:\security\平文.txtのファイルを暗号化し、 Z:\security\平文.txt.暗号.txt のファイル生成終了 Z:\security>
上記実行で、次の『平文.txt.暗号.txt』の名前の暗号化されたファイルが作成されます。
tQKIT4nIgwAQ8K5y4VRc6x3KKO4lf5HJInlD3AFZTYak319iPz4CGw==
Javaで作成した復号プログラムを、
ここよりダウンロードして実験できます。
(ソース:DecryptDES.java)
実行例を示します。
次のように実行します。まず、鍵を選択するダイアログが現れます。
それに対して『suzuki_DESKey.txt』のファイルを選んでいます。
次に上記で作成した『平文.txt.暗号.txt』を選択しています。
これによって出来上がる『平文.txt.暗号.txt.復号.txt』が
復号したファイルで『平文.txt』と内容が一致するはずです。
Z:\security>java DecryptDES _DESKeyファイルを読み取ります。 読み取り内容:Bu9Xs5wx768=byte列 06 EF 57 B3 9C 31 EF AF 復号対象を読み取ります。 Z:\security\suzuki_DESKey.txtで、 Z:\security\平文.txt.暗号.txtのファイルを暗号化し、 Z:\security\平文.txt.暗号.txt.復号.txt のファイル生成終了 Z:\security>
import java.io.*;//FileInputStream, FileOutputStream; import javax.swing.JFileChooser;//ファイルオープンダイアログ用 import java.math.BigInteger;//任意精度の整数クラス import javax.crypto.SecretKeyFactory;//秘密 (対称) 鍵ファクトリ操作用クラス import javax.crypto.spec.DESKeySpec;//DES 鍵用インターフェイス import java.security.Key;//すべての鍵の最上位インタフェース import javax.crypto.SecretKey;//秘密 (対称) 鍵用インターフェイス(上記Keyを継承) import javax.crypto.Cipher;//暗号化および復号化の機能の提供クラス class MakeKeyDES { static final char[] B64 = { //変換に使うテーブル 64進(6bit分)を 16桁 4行で表現している 'A' ,'B' ,'C' ,'D' ,'E' ,'F' ,'G' ,'H' ,'I' ,'J' ,'K' ,'L' ,'M' ,'N' ,'O' ,'P',//00〜0Fの64進に対応する文字 'Q' ,'R' ,'S' ,'T' ,'U' ,'V' ,'W' ,'X' ,'Y' ,'Z' ,'a' ,'b' ,'c' ,'d' ,'e' ,'f',//10〜1Fの64進対応に対応する文字 'g' ,'h' ,'i' ,'j' ,'k' ,'l' ,'m' ,'n' ,'o' ,'p' ,'q' ,'r' ,'s' ,'t' ,'u' ,'v',//20〜2Fの64進対応に対応する文字 'w' ,'x' ,'y' ,'z' ,'0' ,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' ,'+' ,'/' //30〜3Fの64進対応に対応する文字 }; //引数のbyte配列を、Base64の文字列に変換して返す。 public static String encode64(byte a[]) { if (a.length == 0) return ""; StringBuffer s = new StringBuffer();//変換した文字列記憶用 int cnt3 = 0; //3yteカウント用 long data = 0; //変換対象用:3byteを設定し、4文字で取り出す int idx = 0; do{ //data に 3byte分を設定 data <<= 8; if (idx < a.length){ if (a[idx] >= 0){//Javaは符号なしがないので、if文で処理を変える data += a[idx]; } else { data += 256 + a[idx]; //符号なしのコードに変換して加算 } } cnt3++; if (cnt3 == 3){//3byteごとに変換 //dataを4個の64進に対応する文字に変換 int i = (int)(data / (64 * 64 * 64)); //System.out.println(data + "," + i + "," + 256 * 256 * 256); s.append(B64[i]);//1文字目 data = data % (64 * 64 * 64); i = (int)(data / (64 * 64)); s.append(B64[i]);//2文字目 data = data % (64 * 64); i = (int)(data / (64)); s.append(B64[i]);//3文字目 data = data % 64; s.append(B64[(int)data]);//4文字目 data = 0; //次の変換データの準備 cnt3 = 0; } idx++; } while (idx < a.length || cnt3 != 0); int len = s.length(); //文字列の長さ String rtnval = ""; if (a.length % 3 == 2) { return s.substring(0, len - 1) + "="; } else if (a.length % 3 == 1){ return s.substring(0, len - 2) + "=="; } else { return s.toString(); } } public static void main(String arg[]) throws Exception{ java.util.Scanner stdin = new java.util.Scanner(System.in); System.out.println("適当な入力から、共通鍵を作ります。作成されるファイル名:name_DESKey.txt"); System.out.print(" 名前入力>"); String name = stdin.nextLine(); System.out.println(" (起動時間で乱数を作る場合は、単にEnterを入力ください)"); System.out.print(" 乱数の種を数値で入力>"); String r = stdin.nextLine(); // DES 初期化 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); java.util.Random rand = new java.util.Random();//システム起動時間利用 try{ rand = new java.util.Random(Integer.parseInt(r));//適当な乱数の種 } catch(Exception e) { } BigInteger secretNum = new BigInteger(100, rand); System.out.println("作成した共通鍵 =" + secretNum + "(先頭8byteを使用)"); // それを元に DES の鍵を生成する(先頭8byteのみが使用される) DESKeySpec keySpec = new DESKeySpec(secretNum.toByteArray()); byte []keys = keySpec.getKey(); String b64 = encode64(keys); System.out.print(b64); for(int k = 0; k < keys.length; k++) System.out.printf(" %02X" , keys[k]); System.out.println(); FileOutputStream out = new FileOutputStream(name + "_DESKey.txt"); out.write(b64.getBytes());//ベース64にエンコードしてファイル化 out.close(); System.out.println(name + "_DESKey.txtの鍵ファイル生成終了"); } }
import java.io.*;//FileInputStream, FileOutputStream; import javax.swing.JFileChooser;//ファイルオープンダイアログ用 import java.math.BigInteger;//任意精度の整数クラス import javax.crypto.SecretKeyFactory;//秘密 (対称) 鍵ファクトリ操作用クラス import javax.crypto.spec.DESKeySpec;//DES 鍵用インターフェイス import java.security.Key;//すべての鍵の最上位インタフェース import javax.crypto.SecretKey;//秘密 (対称) 鍵用インターフェイス(上記Keyを継承) import javax.crypto.Cipher;//暗号化および復号化の機能の提供クラス class EncryptDES { static final char[] B64 = { //変換に使うテーブル 64進(6bit分)を 16桁 4行で表現している 'A' ,'B' ,'C' ,'D' ,'E' ,'F' ,'G' ,'H' ,'I' ,'J' ,'K' ,'L' ,'M' ,'N' ,'O' ,'P',//00〜0Fの64進に対応する文字 'Q' ,'R' ,'S' ,'T' ,'U' ,'V' ,'W' ,'X' ,'Y' ,'Z' ,'a' ,'b' ,'c' ,'d' ,'e' ,'f',//10〜1Fの64進対応に対応する文字 'g' ,'h' ,'i' ,'j' ,'k' ,'l' ,'m' ,'n' ,'o' ,'p' ,'q' ,'r' ,'s' ,'t' ,'u' ,'v',//20〜2Fの64進対応に対応する文字 'w' ,'x' ,'y' ,'z' ,'0' ,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' ,'+' ,'/' //30〜3Fの64進対応に対応する文字 }; //引数のbyte配列を、Base64の文字列に変換して返す。 public static String encode64(byte a[]) { if (a.length == 0) return ""; StringBuffer s = new StringBuffer();//変換した文字列記憶用 int cnt3 = 0; //3yteカウント用 long data = 0; //変換対象用:3byteを設定し、4文字で取り出す int idx = 0; do { //data に 3byte分を設定 data <<= 8; if (idx < a.length) { if (a[idx] >= 0) {//Javaは符号なしがないので、if文で処理を変える data += a[idx]; } else { data += 256 + a[idx]; //符号なしのコードに変換して加算 } } cnt3++; if (cnt3 == 3) {//3byteごとに変換 //dataを4個の64進に対応する文字に変換 int i = (int)(data / (64 * 64 * 64)); //System.out.println(data + "," + i + "," + 256 * 256 * 256); s.append(B64[i]);//1文字目 data = data % (64 * 64 * 64); i = (int)(data / (64 * 64)); s.append(B64[i]);//2文字目 data = data % (64 * 64); i = (int)(data / (64)); s.append(B64[i]);//3文字目 data = data % 64; s.append(B64[(int)data]);//4文字目 data = 0; //次の変換データの準備 cnt3 = 0; } idx++; } while (idx < a.length || cnt3 != 0); int len = s.length(); //文字列の長さ String rtnval = ""; if (a.length % 3 == 2) { return s.substring(0, len - 1) + "="; } else if (a.length % 3 == 1) { return s.substring(0, len - 2) + "=="; } else { return s.toString(); } } //Base64の文字列から、バイナリデータを求める。 public static byte[] decode64(String s) { int n = s.length() * 3 / 4; if (s.endsWith("==")) n -= 2; else if (s.endsWith("=")) n -= 1; byte[] bi = new byte[n]; int iset = 0; int len = s.length(); int data = 0; int icount = 0; int i; try { for (i = 0; i < len; i++) { //文字を順番に処理する。 char c = s.charAt(i); data <<= 6;// 6ビットシフト icount++; if (c != '=') { //Base64の文字から、テーブル内のインデックスを求める。 if (c >= 'A' && c <= 'Z') { data |= c - 'A'; } else if (c >= 'a' && c <= 'z') { data |= c - 'a' + 0x1a; } else if (c >= '0' && c <= '9') { data |= c - '0' + 0x34; } else if (c == '+') { data |= 0x3e; } else if (c == '/') { data |= 0x3f; } } if (icount == 4) { icount = 0; bi[iset++] = (byte)((data >> 16) & 0x00ff); bi[iset++] = (byte)((data >> 8) & 0x00ff); bi[iset++] = (byte)(data & 0x00ff); data = 0; } } } catch (ArrayIndexOutOfBoundsException e) { // =文字など無効なデータ分の設定は無視 //System.out.println( iset + "::::" + e.toString()); } return bi; } public static void main(String arg[]) throws Exception{ java.util.Scanner stdin = new java.util.Scanner(System.in); System.out.println("_DESKeyファイルを読み取ります。"); javax.swing.JFileChooser chooser = new javax.swing.JFileChooser(new File(".")); chooser.setDialogTitle("DESのkeyのファイル選択ください。"); System.out.println(); if (chooser.showOpenDialog(null) == JFileChooser.CANCEL_OPTION) System.exit(0); String keyfile = chooser.getSelectedFile().getPath();//keyファイルパス File file = new File(keyfile); int size = (int)file.length(); byte bi[] = new byte[size];//ファイルサイズのbyte配列を用意。 FileInputStream is = new FileInputStream(keyfile); is.read(bi);//ファイルバイナリーを一括読み取る。 is.close(); //ファイルを閉じる。 String keyBsae64 = new String(bi); System.out.print("読み取り内容:" + keyBsae64 + "byte列"); byte[] keys = decode64(keyBsae64); // DES 初期化、キーを記憶 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); DESKeySpec keySpec = new DESKeySpec(keys); SecretKey secretKey = keyFactory.generateSecret(keySpec); for (int k = 0; k < keys.length; k++) System.out.printf(" %02X", keys[k]); System.out.println(); System.out.println("暗号化対象を読み取ります。"); chooser.setDialogTitle("暗号化対象のファイル選択ください。"); File srcFile = new File(""); chooser.setSelectedFile(srcFile); if (chooser.showOpenDialog(null) == JFileChooser.CANCEL_OPTION) System.exit(0); srcFile = chooser.getSelectedFile(); size = (int)srcFile.length(); byte[] datas = new byte[size];//ファイルサイズのbyte配列を用意。 is = new FileInputStream(srcFile); is.read(datas);//ファイルバイナリーで一括読み取る。 is.close(); //ファイルを閉じる。 //keysで、datasを暗号して、outbufferに記憶する。 Cipher cipher = Cipher.getInstance("DES");//ストリームやブロック暗号のフレームワーク取得 cipher.init(Cipher.ENCRYPT_MODE, secretKey);//初期化(modeで暗号化なのか復号化なのかの指定) byte[] outbuffer = cipher.doFinal(datas);//modeによる暗号や復号の継続処理を行い、結果を返す //暗号化したデータをBase64に変換してファイル化 keyBsae64 = encode64(outbuffer); FileOutputStream out = new FileOutputStream(srcFile + ".暗号.txt"); out.write(keyBsae64.getBytes()); out.close(); System.out.println(keyfile + "で、\n" + srcFile + "のファイルを暗号化し、\n" + srcFile + ".暗号.txt のファイル生成終了"); } }
import java.io.*;//FileInputStream, FileOutputStream; import javax.swing.JFileChooser;//ファイルオープンダイアログ用 import javax.crypto.SecretKeyFactory;//秘密 (対称) 鍵ファクトリ操作用クラス import javax.crypto.spec.DESKeySpec;//DES 鍵用インターフェイス import java.security.Key;//すべての鍵の最上位インタフェース import javax.crypto.SecretKey;//秘密 (対称) 鍵用インターフェイス(上記Keyを継承) import javax.crypto.Cipher;//暗号化および復号化の機能の提供クラス class DecryptDES { static final char[] B64 = { //変換に使うテーブル 64進(6bit分)を 16桁 4行で表現している 'A' ,'B' ,'C' ,'D' ,'E' ,'F' ,'G' ,'H' ,'I' ,'J' ,'K' ,'L' ,'M' ,'N' ,'O' ,'P',//00〜0Fの64進に対応する文字 'Q' ,'R' ,'S' ,'T' ,'U' ,'V' ,'W' ,'X' ,'Y' ,'Z' ,'a' ,'b' ,'c' ,'d' ,'e' ,'f',//10〜1Fの64進対応に対応する文字 'g' ,'h' ,'i' ,'j' ,'k' ,'l' ,'m' ,'n' ,'o' ,'p' ,'q' ,'r' ,'s' ,'t' ,'u' ,'v',//20〜2Fの64進対応に対応する文字 'w' ,'x' ,'y' ,'z' ,'0' ,'1' ,'2' ,'3' ,'4' ,'5' ,'6' ,'7' ,'8' ,'9' ,'+' ,'/' //30〜3Fの64進対応に対応する文字 }; //引数のbyte配列を、Base64の文字列に変換して返す。 public static String encode64(byte a[]) { if (a.length == 0) return ""; StringBuffer s = new StringBuffer();//変換した文字列記憶用 int cnt3 = 0; //3yteカウント用 long data = 0; //変換対象用:3byteを設定し、4文字で取り出す int idx = 0; do { //data に 3byte分を設定 data <<= 8; if (idx < a.length) { if (a[idx] >= 0) {//Javaは符号なしがないので、if文で処理を変える data += a[idx]; } else { data += 256 + a[idx]; //符号なしのコードに変換して加算 } } cnt3++; if (cnt3 == 3) {//3byteごとに変換 //dataを4個の64進に対応する文字に変換 int i = (int)(data / (64 * 64 * 64)); //System.out.println(data + "," + i + "," + 256 * 256 * 256); s.append(B64[i]);//1文字目 data = data % (64 * 64 * 64); i = (int)(data / (64 * 64)); s.append(B64[i]);//2文字目 data = data % (64 * 64); i = (int)(data / (64)); s.append(B64[i]);//3文字目 data = data % 64; s.append(B64[(int)data]);//4文字目 data = 0; //次の変換データの準備 cnt3 = 0; } idx++; } while (idx < a.length || cnt3 != 0); int len = s.length(); //文字列の長さ String rtnval = ""; if (a.length % 3 == 2) { return s.substring(0, len - 1) + "="; } else if (a.length % 3 == 1) { return s.substring(0, len - 2) + "=="; } else { return s.toString(); } } //Base64の文字列から、バイナリデータを求める。 public static byte[] decode64(String s) { int n = s.length() * 3 / 4; if (s.endsWith("==")) n -= 2; else if (s.endsWith("=")) n -= 1; byte[] bi = new byte[n]; int iset = 0; int len = s.length(); int data = 0; int icount = 0; int i; try { for (i = 0; i < len; i++) { //文字を順番に処理する。 char c = s.charAt(i); data <<= 6;// 6ビットシフト icount++; if (c != '=') { //Base64の文字から、テーブル内のインデックスを求める。 if (c >= 'A' && c <= 'Z') { data |= c - 'A'; } else if (c >= 'a' && c <= 'z') { data |= c - 'a' + 0x1a; } else if (c >= '0' && c <= '9') { data |= c - '0' + 0x34; } else if (c == '+') { data |= 0x3e; } else if (c == '/') { data |= 0x3f; } } if (icount == 4) { icount = 0; bi[iset++] = (byte)((data >> 16) & 0x00ff); bi[iset++] = (byte)((data >> 8) & 0x00ff); bi[iset++] = (byte)(data & 0x00ff); data = 0; } } } catch (ArrayIndexOutOfBoundsException e) { // =文字など無効なデータ分の設定は無視 //System.out.println( iset + "::::" + e.toString()); } return bi; } // DES による暗号化と復号 static public void useDES(int mode, Key key, InputStream in, OutputStream out) throws Exception { Cipher cipher = Cipher.getInstance("DES");//ストリームやブロック暗号のフレームワークを提供 cipher.init(mode, key);//初期化(modeで暗号化なのか復号化なのかの指定) byte[] data = new byte[1024];//バッファ int len; while ((len = in.read(data)) != -1) {//ソースをバッファに読み取り byte[] outbuffer = cipher.update(data, 0, len);//modeによる暗号や復号の継続処理を行い、結果を返す out.write(outbuffer);//ファイルに書き込み } byte[] bytes = cipher.doFinal();//ブロックやフィードバックの端数処理 if (bytes != null) out.write(bytes);//端数を書き込む out.flush(); } public static void main(String arg[]) throws Exception{ java.util.Scanner stdin = new java.util.Scanner(System.in); System.out.println("_DESKeyファイルを読み取ります。"); javax.swing.JFileChooser chooser = new javax.swing.JFileChooser(new File(".")); chooser.setDialogTitle("DESのkeyのファイル選択ください。"); System.out.println(); if (chooser.showOpenDialog(null) == JFileChooser.CANCEL_OPTION) System.exit(0); String keyfile = chooser.getSelectedFile().getPath();//keyファイルパス File file = new File(keyfile); int size = (int)file.length(); byte bi[] = new byte[size];//ファイルサイズのbyte配列を用意。 FileInputStream is = new FileInputStream(keyfile); is.read(bi);//ファイルバイナリーを一括読み取る。 is.close(); //ファイルを閉じる。 String keyBsae64 = new String(bi); System.out.print("読み取り内容:" + keyBsae64 + "byte列"); byte[] keys = decode64(keyBsae64); // DES 初期化、キーを記憶 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); DESKeySpec keySpec = new DESKeySpec(keys); SecretKey secretKey = keyFactory.generateSecret(keySpec); for (int k = 0; k < keys.length; k++) System.out.printf(" %02X", keys[k]); System.out.println(); System.out.println("復号対象を読み取ります。"); chooser.setDialogTitle("復号対象のファイル選択ください。"); File srcFile = new File(""); chooser.setSelectedFile(srcFile); if (chooser.showOpenDialog(null) == JFileChooser.CANCEL_OPTION) System.exit(0); srcFile = chooser.getSelectedFile(); size = (int)srcFile.length(); byte[] datas = new byte[size];//ファイルサイズのbyte配列を用意。 is = new FileInputStream(srcFile); is.read(datas);//ファイルバイナリーで一括読み取る。 is.close(); //ファイルを閉じる。 //データをBase64からデコードして、配列に記憶します。 keyBsae64 = new String(datas); datas = decode64(keyBsae64); //keysで、datasを暗号して、outbufferに記憶する。 Cipher cipher = Cipher.getInstance("DES");//ストリームやブロック暗号のフレームワーク取得 cipher.init(Cipher.DECRYPT_MODE, secretKey);//初期化(modeで暗号化なのか復号化なのかの指定) byte[] outbuffer = cipher.doFinal(datas);//ブロックやフィードバックの端数処理 FileOutputStream out = new FileOutputStream(srcFile + ".復号.txt"); out.write(outbuffer); out.close(); System.out.println(keyfile + "で、\n" + srcFile + "のファイルを復号し、\n" + srcFile + ".復号.txt のファイル生成終了"); } }