Java 數(shù)據(jù)加密和編碼的幾種方式

最近正好在項目中用到數(shù)據(jù)加密,于是從網(wǎng)上查閱一些資料,了解各種加密方式并寫代碼驗證,就在本篇文章中做個總結(jié)吧。

我將從這幾個方面介紹 Java 中的加密方式以及相關(guān)的概念:

1. 異或加密
2. MD5 算法
3. Base64 編碼
4. DES 加密
5. AES 加密
6. RSA 加密

從嚴格意義上來說,MD5 和 Base64 不屬于加密,它們分別是信息摘要算法和編碼方式,但是網(wǎng)上好多人都說 MD5 加密、Base64 加密,我覺得有必要糾正一下。對于其他的幾種加密方式,下面我會一一進行舉例說明。

1. 異或加密

異或運算(xor)有個特點:數(shù) a 兩次異或同一個數(shù) b 仍然為 a,即(a^b)^b=a。利用這個原理可以實現(xiàn)數(shù)據(jù)的加密和解密功能。
舉個栗子:a=10100001,b=00000110

    a=a^b;   // a=10100111
    b=b^a;   // b=10100001,此時 b 等于 a

異或運算直接對二進制數(shù)據(jù)進行操作,對每一位(bit)上的數(shù)據(jù)進行變換。所以輸入和輸出的數(shù)據(jù)長度相同,不占用額外的空間,可以用于字符和文件的加密,效率比較高。下面我們用代碼實踐一下:

    // 加密的密鑰,構(gòu)造一定長度的字節(jié)數(shù)組
    private final static byte[] KEY_BYTES = "Vp6flFpGW86g7hi6MhD3Zl2eThJTjPnIjXE4".getBytes();
    private final static int KEY_LENGTH = KEY_BYTES.length;

    /**
     * 異或運算加密
     *
     * @param input 要加密的內(nèi)容
     * @return 加密后的數(shù)據(jù)
     */
    public static byte[] xorEncode(byte[] input) {
        int keyIndex = 0;
        int length = input.length;
        for (int i = 0; i < length; i++) {
            input[i] = (byte) (input[i] ^ KEY_BYTES[(keyIndex++ % KEY_LENGTH)]);
        }
        return input;
    }

    /**
     * 異或運算解密
     *
     * @param input 要解密的內(nèi)容
     * @return 解密后的數(shù)據(jù)
     */
    public static byte[] xorDecode(byte[] input) {
        int keyIndex = 0;
        int length = input.length;
        for (int i = 0; i < length; i++) {
            input[i] = (byte) (input[i] ^ KEY_BYTES[(keyIndex++ % KEY_LENGTH)]);
        }
        return input;
    }

為了方便查看加密后的內(nèi)容,這里對輸出結(jié)果做了一下 Base64 編碼。

輸入:123456abcdef,輸出:Z0IFUl1aJzooFCIx

2. MD5 編碼

MD5 是將任意長度的數(shù)據(jù)字符串轉(zhuǎn)化成短小的固定長度的值的單向操作,任意兩個字符串不應(yīng)有相同的散列值。因此 MD5 經(jīng)常用于校驗字符串或者文件,因為如果文件的 MD5 不一樣,說明文件內(nèi)容也是不一樣的,如果發(fā)現(xiàn)下載的文件和給定的 MD5 值不一樣,就要慎重使用。

MD5 主要用做數(shù)據(jù)一致性驗證、數(shù)字簽名和安全訪問認證,而不是用作加密。比如說用戶在某個網(wǎng)站注冊賬戶時,輸入的密碼一般經(jīng)過 MD5 編碼,更安全的做法還會加一層鹽(salt),這樣密碼就具有不可逆性。然后把編碼后的密碼存入數(shù)據(jù)庫,下次登錄的時候把密碼 MD5 編碼,然后和數(shù)據(jù)庫中的作對比,這樣就提升了用戶賬戶的安全性。

使用 Java 實現(xiàn)簡單的 MD5 編碼:

    /**
     * 計算字符串的 MD5
     *
     * @param text 原文
     * @return 密文
     */
    public static String md5Encode(String text) {
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            byte[] digest = md.digest(text.getBytes());
            StringBuilder sb = new StringBuilder();
            for (byte b : digest) {
                String hexString = Integer.toHexString(b & 0xFF);
                if (hexString.length() == 1) {
                    hexString = "0" + hexString;
                }
                sb.append(hexString);
            }
            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            logger.error(e);
        }
        return null;
    }

輸入:123456abcdef,輸出:6f3b8ded65bd7a4db11625ac84e579bb

3. Base64 編碼

Base64 編碼是我們程序開發(fā)中經(jīng)常使用到的編碼方法,它用 64 個可打印字符來表示二進制數(shù)據(jù)。這 64 個字符是:小寫字母 a-z、大寫字母 A-Z、數(shù)字 0-9、符號"+"、"/"(再加上作為墊字的"=",實際上是 65 個字符),其他所有符號都轉(zhuǎn)換成這個字符集中的字符。Base64 編碼通常用作存儲、傳輸一些二進制數(shù)據(jù)編碼方法,所以說它本質(zhì)上是一種將二進制數(shù)據(jù)轉(zhuǎn)成文本數(shù)據(jù)的方案。

在 Java 中使用 Base64 很簡單,系統(tǒng)的 API 已經(jīng)封裝好方法,我們直接調(diào)用即可。

// 編碼
 String encode = Base64.encodeToString(" 123456abcdef".getBytes(), Base64.DEFAULT);

// 解碼
 byte[] decodeByte = Base64.decode(encode .getBytes(), Base64.DEFAULT);
String decode = new String(decodeByte);

輸入:123456abcdef,輸出:MTIzNDU2YWJjZGVm

無論是編碼還是解碼都會有一個參數(shù) flags,系統(tǒng) API 提供了以下幾種:

  • DEFAULT:表示使用默認的方法來加密。
  • NO_PADDING:表示省略加密字符串最后的 "="。
  • NO_WRAP:表示省略所有的換行符(設(shè)置后 CRLF 就會失去作用)。
  • CRLF:表示使用 CR、LF 這一對作為一行的結(jié)尾而不是 Unix 風(fēng)格的 LF。
  • URL_SAFE:表示加密時不使用對 URL 和文件名有特殊意義的字符來作為加密字符,就是以 "-" 和 "_" 代替 "+" 和 "/"。

4. DES 加密

DES 是一種對稱加密算法,所謂對稱加密算法就是:加密和解密使用相同密鑰的算法。DES 加密算法出自 IBM 的研究,后來被美國政府正式采用,之后開始廣泛流傳。但近些年使用越來越少,因為 DES 使用 56 位密鑰,以現(xiàn)代的計算能力,24 小時內(nèi)即可被破解。

順便說一下 3DES(Triple DES),它是 DES 向 AES 過渡的加密算法,使用 3 條 56 位的密鑰對數(shù)據(jù)進行三次加密。是 DES 的一個更安全的變形。它以 DES 為基本模塊,通過組合分組方法設(shè)計出分組加密算法。比起最初的 DES,3DES 更為安全。

使用 Java 實現(xiàn) DES 加密解密,注意密碼長度要是 8 的倍數(shù)。加密和解密的 Cipher 構(gòu)造參數(shù)一定要相同,不然會報錯。

    /* 加密使用的 key */
    private final static byte[] KEY_BYTES = "Vp6fhlFXKpGW8k6QPRg7Q6Jb7HyAhRi6MIhJ2YtGD3Zl26eTthJTj5PnIjXH5EI4".getBytes();

   /**
     * DES 加密
     *
     * @param content 待加密內(nèi)容
     * @param key     加密的密鑰
     * @return 加密后的字節(jié)數(shù)組
     */
    public static byte[] encryptDES(byte[] content, byte[] key) {
        try {
            SecureRandom random = new SecureRandom();
            DESKeySpec desKey = new DESKeySpec(key);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretKey = keyFactory.generateSecret(desKey);
            // DES 是加密方式, EBC 是工作模式, PKCS5Padding 是填充模式
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, random);
            return cipher.doFinal(content);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

 /**
     * DES 解密
     *
     * @param content 待解密內(nèi)容
     * @param key     解密的密鑰
     * @return 解密的數(shù)據(jù)
     */
    public static byte[] decryptDES(byte[] content, byte[] key) {
        try {
            SecureRandom random = new SecureRandom();
            DESKeySpec desKey = new DESKeySpec(key);
            SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
            SecretKey secretKey = keyFactory.generateSecret(desKey);
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, random);
            return cipher.doFinal(content);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

輸入:123456abcdef,輸出:j1kR1+ZraO2Tg78dHueoTg==

5. AES 加密

高級加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學(xué)中又稱 Rijndael 加密法,是美國聯(lián)邦政府采用的一種區(qū)塊加密標準。這個標準用來替代原先的 DES,已經(jīng)被多方分析且廣為全世界所使用。簡單說就是 DES 的增強版,比 DES 的加密強度更高。

AES 與 DES 一樣,一共有四種加密模式:電子密碼本模式(ECB)、加密分組鏈接模式(CBC)、加密反饋模式(CFB)和輸出反饋模式(OFB)。關(guān)于加密模式的介紹,推薦這篇文章:高級加密標準AES的工作模式(ECB、CBC、CFB、OFB)

    /* 加密使用的 key */
    private static final String AES_KEY = "KUbHwTqBy6TBQ2gN";
    /* 加密使用的 IV */
    private static final String AES_IV = "pIbF6GR3XEN1PG05";

 /**
     * AES 加密
     *
     * @param content 待解密內(nèi)容
     * @param key     密鑰
     * @return 解密的數(shù)據(jù)
     */
    public static byte[] encryptAES(byte[] content, byte[] key) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
            // AES 是加密方式, CBC 是工作模式, PKCS5Padding 是填充模式
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            // IV 是初始向量,可以增強密碼的強度
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(AES_IV.getBytes()));
            return cipher.doFinal(content);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

    /**
     * AES 解密
     *
     * @param content 待解密內(nèi)容
     * @param key     密鑰
     * @return 解密的數(shù)據(jù)
     */
    public static byte[] decryptAES(byte[] content, byte[] key) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(AES_IV.getBytes()));
            return cipher.doFinal(content);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

輸入:123456abcdef,輸出:ho9cn9SvmeisfJy6Pv96oQ==

6. RSA 加密

RSA算法是一種非對稱加密算法,所謂非對稱就是該算法需要一對密鑰,若使用其中一個加密,則需要用另一個才能解密。目前它是最有影響力和最常用的公鑰加密算法,能夠抵抗已知的絕大多數(shù)密碼攻擊。從提出到現(xiàn)今的三十多年里,經(jīng)歷了各種攻擊的考驗,逐漸為人們接受,普遍認為是目前最優(yōu)秀的公鑰方案之一。

該算法基于一個的數(shù)論事實:將兩個大質(zhì)數(shù)相乘十分容易,但是想要對其乘積進行因式分解卻極其困難,因此可以將乘積公開作為加密密鑰。由于進行的都是大數(shù)計算,RSA 最快的情況也比 DES 慢上好幾倍,比對應(yīng)同樣安全級別的對稱密碼算法要慢 1000 倍左右。所以 RSA 一般只用于少量數(shù)據(jù)加密,比如說交換對稱加密的密鑰。

使用 RSA 加密主要有這么幾步:生成密鑰對、公開公鑰、公鑰加密私鑰解密、私鑰加密公鑰解密。

    public static final String AES = "AES";
    public static final String ECB_PKCS1_PADDING = "RSA/ECB/PKCS1Padding";

   /**
     * 隨機生成 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) {
            logger.error(e);
        }
        return null;
    }

    /**
     * 公鑰加密
     *
     * @param data      原文
     * @param publicKey 公鑰
     * @return 加密后的數(shù)據(jù)
     */
    public static byte[] encryptByPublicKey(byte[] data, byte[] publicKey) {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        try {
            KeyFactory kf = KeyFactory.getInstance(RSA);
            PublicKey keyPublic = kf.generatePublic(keySpec);
            Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, keyPublic);
            return cipher.doFinal(data);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

    /**
     * 私鑰加密
     *
     * @param data       待加密數(shù)據(jù)
     * @param privateKey 密鑰
     * @return 加密后的數(shù)據(jù)
     */
    public static byte[] encryptByPrivateKey(byte[] data, byte[] privateKey) {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
        try {
            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);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

    /**
     * 公鑰解密
     *
     * @param data      待解密數(shù)據(jù)
     * @param publicKey 密鑰
     * @return 解密后的數(shù)據(jù)
     */
    public static byte[] decryptByPublicKey(byte[] data, byte[] publicKey) {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKey);
        try {
            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);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

    /**
     * 私鑰解密
     *
     * @param data  待解密的數(shù)據(jù)
     * @param privateKey 私鑰
     * @return 解密后的數(shù)據(jù)
     */
    public static byte[] decryptByPrivateKey(byte[] data, byte[] privateKey) {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(privateKey);
        try {
            KeyFactory kf = KeyFactory.getInstance(RSA);
            PrivateKey keyPrivate = kf.generatePrivate(keySpec);
            Cipher cipher = Cipher.getInstance(ECB_PKCS1_PADDING);
            cipher.init(Cipher.DECRYPT_MODE, keyPrivate);
            return cipher.doFinal(data);
        } catch (Exception e) {
            logger.error(e);
        }
        return null;
    }

密鑰對生成后是一串灰常長特別長的數(shù)據(jù),大家可以對其 Base64 后看看公鑰和隨機性如何。使用測試數(shù)據(jù)123456abcdef加密過、Base64 編碼后的結(jié)果如下。加密這么短的字符串竟然生成這么長的密文,難怪 RSA 算法這么慢!

密文 Base64 后:X6yx1XfkVk4DZpnzcCSZr2oK+WMP7Azm4fBcGNEwWPrRdtf9isfMeKgQsI6kqOF5Vb5b5IYAIqHZRE5QcDbIM/3bTWVTVg/t7enGCUSxValIvJ/A37syWTUXlh59DZzBMgzG4rbziGCc8CGyO03XFq8gCncr4NMZXQwkKI8Alds=

一般來說,客戶端和服務(wù)端的通信過程是這樣的:服務(wù)端生成 RSA 加密的密鑰對,把公鑰給客戶端,私鑰偷偷保留??蛻舳嗽谑状问褂霉€加密數(shù)據(jù),然后發(fā)送給服務(wù)端。服務(wù)端接收并處理后,會把對稱加密的密鑰下發(fā)給客戶端??蛻舳私邮盏綄ΨQ加密的密鑰,以后的通信就會使用對稱加密的方式。當然也可以由客戶端生成對稱加密的密鑰,然后用公鑰加密發(fā)給服務(wù)端。這樣在雙方交換密鑰時保證了安全,之后的對稱加密保證了效率。

好了,關(guān)于 Java 加密和編碼的方式就先介紹這么多,歡迎大家留言交流~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,563評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,694評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,672評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,965評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,690評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,019評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,013評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,188評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,718評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,438評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,667評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,149評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,845評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,252評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,590評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,384評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內(nèi)容