情報の機密性確保のために、内容を隠蔽することを暗号化(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 のファイル生成終了");
}
}