2016年3月16日 星期三

Google Play 驗證 In-app Billing 電子收據 - 實作篇

經過上一篇介紹如何進行 Google Play 驗證 In-app Billing  如何進行設定後,本篇將解說驗證的工作流程。
Google API 使用 OAuth 2.0 認證機制,在認證之前我們需要先產生 JWT 向 Google 要求一組 Token,
然後再將 Google 給的 Token 在 Call API 時一并放在 Request 內一起送過去。
什麼是 JWT呢?簡單來說它是一個開放標準基於 json 的一種認證方式可以在不同的網域間共享資訊。
想更進一步的了解 JWT可以到 http://self-issued.info/docs/draft-ietf-oauth-jwt-bearer.html

驗證的第一步就是先向 Google 要來一組 Token,往後的 API 呼叫都需要用到它,
附帶一提 Token 有時效性(印象中最高可設定1小時)

///
/// 向 Google 索取存取API所需的 Token
/// 
public static void FetchGoogleAccessToken() {
    //JOSE Header 設定使用何種加密方式進行簽章
    var header = new { typ = "JWT", alg = "RS256" };
    var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
    var issueTime = DateTime.UtcNow;
    var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
    var exp = (int)issueTime.AddMinutes(60).Subtract(utc0).TotalSeconds;
    //ClamsSet傳送到 Google 進行 Auth 時的資訊
    var claimset = new {
        iss = /*上一篇有提到的  EMAIL ADDRESS */,
        scope = "https://www.googleapis.com/auth/androidpublisher",
        aud = "https://accounts.google.com/o/oauth2/token",
        iat,
        exp
    };

    // Encoded header
    var headerSerialized = JsonConvert.SerializeObject(header);
    var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
    //網路上找的到 Base64UrlEncode 不貼碼了
    var headerEncoded = Base64UrlEncode(headerBytes);

    // Encoded claimset
    var claimsetSerialized = JsonConvert.SerializeObject(claimset);
    var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
    //網路上找的到 Base64UrlEncode 不貼碼了
    var claimsetEncoded = Base64UrlEncode(claimsetBytes);
    //C# 6.0 的語言 可替換成 string.Format("{0}.{1}",headerEncoded,claimsetEncoded)
    var input = $"{headerEncoded}.{claimsetEncoded}";
    var inputBytes = Encoding.UTF8.GetBytes(input);

    // signiture
    var certificate = new X509Certificate2(
        Path.Combine(
            /*存放P12檔的路徑*/,
            /*P12檔的檔名*/),
        "notasecret");
    var rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
    var cspParam = new CspParameters {
        KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
        KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
    };
    var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
    var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
    var signatureEncoded = Base64UrlEncode(signatureBytes);

     //JWT 需要將三個連在一起︰Header,Claims Set 和 Sign 然後用 . 隔開
    var jwt = $"{headerEncoded}.{claimsetEncoded}.{signatureEncoded}";
    var r = (HttpWebRequest)WebRequest.Create("https://accounts.google.com/o/oauth2/token");
    r.Method = "POST";
    r.ContentType = "application/x-www-form-urlencoded";
    r.UserAgent = string.Format(Section.Get.Common.Culture, " Mozilla/4.0 (compatible; Win32; {0}.{1})", 1, 0);
    ServicePointManager.ServerCertificateValidationCallback = (sender, certificates, chain, sslPolicyErrors) => true;
    var postData = $@"grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion={jwt}";
    var postBytes = Encoding.UTF8.GetBytes(postData);
    r.ContentLength = postBytes.Length;
    using (var postStream = r.GetRequestStream()) {
        postStream.Write(postBytes, 0, postBytes.Length);
        postStream.Close();
    }
    Task.Factory.FromAsync(r.BeginGetResponse, r.EndGetResponse, null).ContinueWith(t => {
        try {
            var result = t.Result.GetResponseStream();
            if (null == result) return;
            using (var sr = new StreamReader(result, Encoding.UTF8)) {
                var token = sr.ReadToEnd();
                //GoogleToken 這個類別只有三個欄位 string access_token、string token_type、int expires_in
                var tokenObj = JsonConvert.DeserializeObject(token);
                //tokenObj.access_token 需要保存下來
                Log.Info($"NewAccessToken={tokenObj.access_token}");
                sr.Close();
            }
        }
        catch (Exception ex) {
            Log.Error(ex.Message, ex);
        }
    }, TaskContinuationOptions.ExecuteSynchronously);
}

最後,當玩家玩成儲值流程時先別急著將道具或代幣給玩家,先向 Google 問一下這位玩家剛剛是否有真的消費
實作的方式可以參考下面的連結進行實作
https://developers.google.com/android-publisher/api-ref/purchases/products/get#request

2014年10月25日 星期六

Google Play 驗證 In-app Billing 電子收據 - 設定篇

      Android 遊戲的開發者在提供遊戲內購買的功能時都要面對的問題:如何驗證  In-app Billing  電子收據,網路上
可以找到不少的方法可以繞過 Android In-app Billing 的驗證,用 Fake IAB receipt 欺騙我們開發的 Android  遊戲。
要防治這個問題最簡單的方式就是將 IAB receipt 向 Google Play 進行驗證,下圖(臨時用 Google 文件畫的)是我們
製作的遊戲在遊戲內購買功能的流程,因採用負載平衡所以登入的動作會多二個步驟,整個流程大致如下︰
1.登入Master Server
2.Master Server回傳負載最輕的伺服器
3.連線到遊戲伺服器
4.Client 發送儲值要求 要Game Server
5.Game Server回傳我方的訂單編號給 Client
6.Client 向 Google Play 請求儲值
7.Google Play 回傳交易憑證到 Client
8.Client 要求結束儲值並將交易憑證給 Game Server
9.Game server 向 Google Play 驗證交易憑證
10.Google Play 回報交易結果
11.Game Server 通知儲值結果給 Client
App 若只做到第7個步驟時就會將玩家儲值或購買的東西給玩家時,就能被 Fake  IAB receipt 所斯騙。
要進行第8個步驟前需要先到 Google Play  的管理中心進行設定,登入 Google Play Developer Console 必須是帳戶擁有者才可以進行設定,接到到設定 -> API 存取權的頁面,接著點擊建立新專案
連著會建立好專案之後再點擊建立OAuth用戶端之後會轉到 Google Console
緊接著再點擊 Create new Client ID(看你使用的Browser的語系而定,繁中版會顯示中文)
接著選第二種 Server account
建立好之後會出現下面的資訊,過程中會下載一個 p12 憑證檔案,請保存好,此外再記好 EMAIL ADDRESS 下篇會用到。
接著點擊左方選單的 APIs 將 Google Play Android Developer API 開啟
最後一步,必需將剛建立的帳號給予管理員的權限才行。
      透過上述的設定之後,接下來我們就可以在遊戲伺服器內用剛才建立的 Server Account 向 Google Play 進行交易憑證的驗證,
防止收到 Fake IAB receipt 時仍將點數或道具交給玩家。

2014年10月16日 星期四

MSSQL 進行資料庫還原時出現 3154 的錯誤解決方法

剛剛是附加的問題,現在則是遇上了資料庫還原的問題,
 在SQL Server 2014版本中還原從SQL Server 2008備份出來的數據庫時,
會出現錯誤:「備分組包含現有的 '***' 資料庫以外的資料庫備份。RESTORE DATABASE 正在異常結束。 (Microsoft SQL Server,錯誤: 3154)」。 
久遠前學習到的在SQL Server要作資料庫還原時,
需要首先建立一個資料庫(舊的也行總之要先有一個資料庫實體),然後才能可以進行資料庫還原 
1.建立空的資料庫
2.在資料庫上按右鍵工作-〉還原-〉檔案和檔案群組
設定好選項後就出現下歹3154的錯誤

突然發現  2012、2014版上可以直接在在資料庫的ROOT層上操作還原
設定上和原有的一模一樣,只差在一個有建好資料庫,一個沒建資料庫。
總之,這個問題就這樣的被排解掉了。打個文記錄一下

MSSQL 進行資料庫附加時出現 5123 的錯誤解決方法

昨天機房內的主機的硬碟突然掛了(RAID 0),就在重灌系統後,正要復原SQL SERVER的資料庫時發了一段小插曲,因為資料庫的實體檔案沒放在系統碟內,很自然的就使用“附加"的動作來掛回去資料庫到 SQL SERVER中,但它確出現了下面的錯誤訊息。

一時間也不知道要怎處理,上GOOGLE查閱,大家也都說把只要把.mdf檔和.ldf加入 NETWORK SERVICE就可以解決這個問題,但我加了也是出現相同的錯誤。並且發現檔上有個不存在的帳號權限,推測檔案就是缺少了這個帳號的權限才會出現5123的錯誤。
有了方向就可以出發向偉大的Google尋求解答,在下了N個關鍵字找尋後,MSDN還是吃微軟飯的人的庇護所, 按造文件上的說明一步一步的操作,果然解決了資料庫檔不能附加的問題。
設定 Database Engine 對檔案系統的存取權限

2014年5月31日 星期六

AzDG可逆加密演算法 For Ruby

╮╯▂╰╭ AzDG再度登板
#/*Sava as AzDG.rb*/
#!/usr/bin/env ruby
# encoding: UTF-8
require 'digest'
require 'base64'

class AzDG
    attr_accessor :cipher
    def initialize(cipher = "Private key")
        @cipherHash = Digest::MD5.hexdigest(cipher)
    end

    def cipherEncode(inputData)   
        outData = Array.new(inputData.length)
        for i in (0..inputData.length-1) 
            outData[i] = (inputData[i] ^ @cipherHash[i%32].ord)
        end    
        return outData
    end

    def Encrypt(inputData)  
        noise = Digest::MD5.hexdigest(Time.new.strftime("%Y-%m-%d %H:%M:%S"))
        aryInputData = inputData.bytes.to_a        
        outData = Array.new(aryInputData.length * 2)
        for i in (0..aryInputData.length-1) 
            outData[(i*2)] = noise[i%32].ord     
            outData[(i*2)+1] = aryInputData[i] ^ noise[i%32].ord 
        end        
        return Base64.encode64(cipherEncode(outData).pack('c*'))
    end

    def Decrypt(inputData) 
        decodeSourceText = cipherEncode(Base64.decode64(inputData).bytes.to_a)
        outData = Array.new()
        i = 0           
        while i < decodeSourceText.length do 
            outData.push(decodeSourceText[i].ord ^ decodeSourceText[i+1].ord)    
            i += 2
        end
        return outData.pack('c*').force_encoding('UTF-8')
    end
end     

使用方式

require './AzDG'
azdg = AzDG.new
var  = azdg.Encrypt("張學友")
puts "#{var}"
puts azdg.Decrypt(var)

2014年3月14日 星期五

AzDG可逆加密演算法 For Javascript

老狗玩不出新把戲( ╮╯▂╰╭)AzDG再度上場,本次的版本是給Node.js於後端使用,請勿使用在前端頁面中,這樣的話私鑰就曝光了。
使用前請先用NPM安裝MD5的套件( MD5 ),此外也沒有提供額外的字元集支持,請使用UTF-8字元編碼。
程式中的 byte to utf-8 字串的程式碼來源在這裡
/*Sava as AzDG.js*/
(function() {
    var md5 = require('MD5');    
    var utf8 =
    {
        encode: function(str) {
            var len = str.length;
            var result = [];
            var code;
            var i;
            for (i = 0; i < len; i++) {
                code = str.charCodeAt(i);
                if (code <= 0x7f) {
                    result.push(code);
                } else if (code <= 0x7ff) { // 2 bytes                     
                    result.push(0xc0 | (code >>> 6 & 0x1f),
                        0x80 | (code & 0x3f));
                } else if (code <= 0xd700 || code >= 0xe000) { // 3 bytes
                    result.push(0xe0 | (code >>> 12 & 0x0f),
                        0x80 | (code >>> 6 & 0x3f),
                        0x80 | (code & 0x3f));
                } else { // 4 bytes, surrogate pair
                    code = (((code - 0xd800) << 10) | (str.charCodeAt(++i) - 0xdc00)) + 0x10000;
                    result.push(0xf0 | (code >>> 18 & 0x07),
                        0x80 | (code >>> 12 & 0x3f),
                        0x80 | (code >>> 6 & 0x3f),
                        0x80 | (code & 0x3f));
                }
            }
            return result;
        },
        decode: function(array) {
            var out, i, len, c;
            out = "";
            len = array.length;
            i = 0;
            while (i < len) {
                c = array[i++];
                switch (c >> 4) {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                    // 0xxxxxxx
                    out += String.fromCharCode(c);
                    break;
                case 12:
                case 13:
                    // 110x xxxx   10xx xxxx
                    out += String.fromCharCode(((c & 0x1F) << 6) | (array[i++] & 0x3F));
                    break;
                case 14:
                    // 1110 xxxx  10xx xxxx  10xx xxxx
                    out += String.fromCharCode(((c & 0x0F) << 12) | 
                      ((array[i++] & 0x3F) << 6) |
                      ((array[i++] & 0x3F) << 0));
                    break;
                }
            }
            return out;
        }
    }

    var AzDG = {
        cipher: 'Private key',
        cipherEncode: function(inputData) {
            var cipherHash = utf8.encode(md5(this.cipher));
            var outData = new Array(inputData.length);
            for (var i = 0; i < inputData.length; i++) {
                outData[i] = (inputData[i] ^ cipherHash[i % 32]);
            }
            return outData;
        },
        encrypt: function(sourceText) {
            var inputData  = utf8.encode(sourceText); 
            var noise = md5(Math.round((new Date()).getTime()).toString()); 
            var outData = new Array(inputData.length * 2);
            for (var j = 0; j < inputData.length; j++) {
                outData[(j * 2)] = noise[j % 32];
                outData[(j * 2) + 1] = inputData[j] ^ noise[j % 32];
            }           
            return new Buffer(this.cipherEncode(outData)).toString('base64')
        },
        decrypt: function(sourceText) {
            var decodeSourceText = this.cipherEncode(new Buffer(sourceText, 'base64'));
            var size = Math.ceil(decodeSourceText.length / 2);
            var outData = new Array(size);
            for (var j = 0; j < size; ++j) {
                outData[j] = (decodeSourceText[(j * 2)] ^ decodeSourceText[(j * 2) + 1]);
            }
            return utf8.decode(outData);
        }
    }
    module.exports.getInstance = function() {
        return AzDG; 
    };
})();

使用方式

var azdg =  require('./AzDG').getInstance();
encode = azdg.encrypt('她來聽我 的演唱會 在十七歲的初戀 第一次約會');
console.log('decode:' +azdg.decrypt(encode));

2014年3月6日 星期四

日本之行見聞錄

前陣子公司接到一家日本公司的委託案,協助他們進行網頁遊戲的開發,
當開發期結束準備要上線營運時,日本公司卻表示搞不定主機的設定及平台的界接。
開發商不同於營運商,遊戲要上那個平台,要用那一家IDC或AWS,
通常開發商都不會介入,日本公司原先也以為依他們的能力可以搞定相關的程式碼串接或主機設定,
也因此有這個機會前往日本出差,一到當地就被日本的地鐵給難倒了(是的,沒有人來接機 ╮╯▂╰╭ ),
幾經波折之後總算抵達大阪市西区靭本町下榻的Inn(是的,不是商務飯店是Inn ╮╯▂╰╭ )。
綜合這幾天的過程對於日本這個國家有了不一樣的認識
這個國家好安靜,所處的地方都見不到摩托車和公車(但有區與區的長程巴士,類似台灣的台北<->高雄),
而且不像學生時代老師上課所說的那樣,日本是個地狹人稠的地方,人非但沒有很多,建築物內部也滿寬廣的。
沒到日本前我也以為日本的地鐵系統和台北捷運一樣的新穎,事實上大部份的地鐵車站內都有點舊,
很有萬華那種的feel,飲食的部份就我來說台灣日本並沒有什麼太大的差異,但是口味比台灣還要鹹,吃過的拉麵和台灣賣的差不多,
大阪當地傳統的食物為炸物沾醬油(不是一般的醬油店家有調配過),當地時間10-11點還是有人上館子吃晚餐,
還有日本室內沒禁煙,可以邊吃飯邊抽煙(是的,這麼先進的國家居然可以在室內抽煙 ╮╯▂╰╭),
可能是口味偏鹹導致食量不大,日本國人中少有胖子,期間只遇到一位胖子是講日文。
大馬路很平,但小巷子內還是和台灣一樣,東補一塊西補一塊,但是品質還是比台灣好。
這裡也不像台灣那樣到處都有修車廠,一般的汽車保養大多在加油站就處理掉了。
日本的物價對照收入相較於台灣來說我覺得是低的,
日本人的收入大約是台灣的3倍,以一碗拉麵來比較,這碗麵日幣1500元依今天的匯率3。36來算約台幣446元
除以3之後大約148元,這碗麵的內容物大約是二個正常人才吃的完(燒肉好像不用錢一樣好多,起碼有八塊以上),
而且加麵還不加價。
最後講到作事態度,在國外好像只要說︰我不會、我不知道、這不是我的工作、我在休假、叭啦叭啦叭啦之類,
就可以將工作讓給其他人來處理,這點莫說在日本,先前接的義大利的案子也差不多是這個樣。


2013年12月27日 星期五

AzDG可逆加密演算法 For Dart

Google 2011 年時所推出的語言,據說是要取代現有的Javascript,先前已有稍為看了一下,
因為沒有專案在使用這門語言,所以就沒有進一步的了解據聞公司明年的合作伙伴採用Dart來作為專案的主要撰寫語言,
所以再度把AzDG拿來練手,和google 另一門語言 Go 一樣還滿易學的。


import 'package:crypto/crypto.dart' show MD5, CryptoUtils;
import 'package:utf/utf.dart';

class AzDG {
    String _cipher = 'Private key';
    List< int > _cipherHash;
    AzDG({String cipher}) {
        if (cipher != null) {
            _cipher = cipher;
        }
        var md5 = new MD5();
        md5.add(this._cipher.codeUnits);
        _cipherHash = CryptoUtils.bytesToHex(md5.close()).codeUnits;
    }

    List < int > _cipherEncode(List < int > inputData) {
        var outData = new List < int > (inputData.length);
        var loopCount = inputData.length;
        for (var i = 0; i & lt; loopCount; i++) {
            outData[i] = inputData[i] ^ _cipherHash[i % 32];
        }
        return outData;
    }

    String Decrypt(String sourceText) {
        var decodeSourceText = _cipherEncode(CryptoUtils.base64StringToBytes(sourceText));
        var size = (decodeSourceText.length~ / 2).toInt();
        var outData = new List < int > (size);
        for (int j = 0; j < size; ++j) {
            outData[j] = (decodeSourceText[(j * 2)] ^ decodeSourceText[(j * 2) + 1]);
        }
        return decodeUtf8(outData);
    }

    String Crypt(String sourceText) {
        var now = new DateTime.now();
        var md5 = new MD5();
        md5.add(now.toString().codeUnits);
        var noise = CryptoUtils.bytesToHex(md5.close()).codeUnits;
        var inputData = encodeUtf8(sourceText);
        var outData = new List < int > (inputData.length * 2);
        for (var j = 0; j < inputData.length; j++) {
            outData[(j * 2)] = noise[j % 32];
            outData[(j * 2) + 1] = inputData[j] ^ noise[j % 32];
        }
        return CryptoUtils.bytesToBase64(_cipherEncode(outData));
    }
}

2013年12月17日 星期二

Java 解開ZIP檔案

奮戰了幾天,雖然實作了JSP在線解開ZIP檔案的功能
但是有個致命的缺點
ZIP檔的檔案名稱不能含有非ASCII的字元(ISO-8859-1)
一遇上非ISO-8859-1編碼的字串
下面的程式就會停擺
google一下有個package可以解決這個問題
目前還在研究這個package
有收穫再貼上來分享
先看看下面簡易的在線解壓功能
老實說,效能不怎好
解相同的檔案較PHP及C#都要來的慢..


package com.jIAn.decompress;
import java.lang.*;
import java.io.*;
import java.util.zip.*;
public class zip
{
    public zip(){}
    public void DecompressFile(String arg_zipFileName, String arg_outputDirectory) throws Exception
    {
        ZipInputStream objZipInputStream = new ZipInputStream(new FileInputStream(arg_zipFileName));
        ZipEntry objZipEntry;
        InputStream objInputStream = null;
        File objFile ;
        int intStreamLength ;        
        while ((objZipEntry = objZipInputStream.getNextEntry()) != null) 
        {
            
            if (objZipEntry.isDirectory()) 
            {
                String strDirectoryName = objZipEntry.getName();
                strDirectoryName = strDirectoryName.substring(0, strDirectoryName.length() - 1);
                objFile = new File(arg_outputDirectory + File.separator + strDirectoryName);
                objFile.mkdir();
                System.out.println("建立資料夾 " + arg_outputDirectory + File.separator + strDirectoryName);
            }
            else 
            {
                objFile = new File(arg_outputDirectory + File.separator + objZipEntry.getName());
                objFile.createNewFile();
                FileOutputStream objFileOutputStream = new FileOutputStream(objFile);                
                while ((intStreamLength = objZipInputStream.read()) != -1)
                {
                    objFileOutputStream.write(intStreamLength);
                }
                objFileOutputStream.close();
            }
            System.out.println("解壓 " + objZipEntry.getName()+".......ok");
        }
        objZipInputStream.close();
    }

}
如何使用?
建立物件
zip objZip = new zip();
objZip.DecompressFile(欲解開的Zip檔,解到那一個目錄);
objZip.DecompressFile("C:/zip/123.zip","C:\\zip\\123");
還有一個要注意的地方
如果解壓到多層路徑需要先行建立多層路徑的目錄

Java DES 範例


package com.jIAn.crypt;
import javax.crypto.*;

public class DES
{
    private static String strDefaultKey = "jIAn";    //預設的金鑰
    private StringBuffer objSb             = null;
    private Cipher objCipher             = null;
    private java.security.Key objKey    = null;
    private int intStringLength            = 0;
    private int intTemp                    = 0;
    
    //預設建構子
    public DES() throws Exception
    {        
        this(strDefaultKey);
    }

    //自訂密鑰
    public DES(String arg_strKey) throws Exception 
    {
        setKey(arg_strKey.getBytes());
        objCipher = Cipher.getInstance("DES");
    }

    //從指定的字串製成密鑰,密鑰所需的字元陣列長度為8位,不足及超過都要處理
     private void setKey(byte[] arg_strPrivateKey) throws Exception 
     {         
         byte[] arrTempByteArray = new byte[8];
         // 將原始字元陣列轉換為8位
         for (int i = 0; i < arg_strPrivateKey.length && i < arrTempByteArray.length; i++)
         {
             arrTempByteArray[i] = arg_strPrivateKey[i];
         }
         // 設定密鑰
         objKey = new javax.crypto.spec.SecretKeySpec(arrTempByteArray, "DES");    
     }
     
     //將byte陣列轉換16進制值的字串,如:byte[]{1,18}轉換為:0112     
    public String byte2Hex(byte[] arg_bteArray) throws Exception 
    {
        intStringLength = arg_bteArray.length;    
        objSb = new StringBuffer(intStringLength * 2);
        for (int i = 0; i < intStringLength; i++)
        {
            intTemp = (int)arg_bteArray[i];
            //負數需要轉成正數
            if(intTemp < 0) 
            {
                intTemp = intTemp + 256;
            }
            // 小於0F需要補0
            if (intTemp < 16)
            {
                objSb.append("0");
            }
            objSb.append(Integer.toString(intTemp, 16));
        }
        return objSb.toString();
     }

    
    //將16進制值的字串轉成byte陣列        
    public byte[] hex2Byte(String arg_strHexString) throws Exception 
    {
        byte[] arrByteDAta = arg_strHexString.getBytes();
        intStringLength = arrByteDAta.length;
        byte[] aryRetuenData = new byte[intStringLength / 2];
        for (int i = 0; i < intStringLength; i = i + 2)
        {
            aryRetuenData[i / 2] =  (byte)Integer.parseInt(new String(arrByteDAta, i, 2), 16);
        }
        return aryRetuenData;
    }

    //加密字串
     public byte[] doEncrypt(byte[] arg_bteArray) throws Exception
     {
         objCipher.init(Cipher.ENCRYPT_MODE, objKey);
         return objCipher.doFinal(arg_bteArray);
     }
     
     public String encrypt(String arg_strToEncriptString) throws Exception 
     {
         return byte2Hex(doEncrypt(arg_strToEncriptString.getBytes()));
     }
     
     public byte[] doDecrypt(byte[] arg_bteArray) throws Exception 
     {
        objCipher.init(Cipher.DECRYPT_MODE, objKey);     
        return objCipher.doFinal(arg_bteArray);
     }
     //解密字串
     public String decrypt(String arg_strToDecriptString) throws Exception 
     {
         return new String(doDecrypt(hex2Byte(arg_strToDecriptString)));
     }
}
測試

package com.jIAn.crypt;
import java.util.*;
public class Test 
{
    public static void main(String[] args) 
    {
        try 
        {
            String test = "jIAn";
            DES des = new DES("jIAn");//自定義密鑰
            System.out.println("加密前的字符:"+test);
            System.out.println("加密後的字符:"+des.encrypt(test));
            System.out.println("解密後的字符:"+des.decrypt(des.encrypt(test)));            
        } 
        catch (Exception e)
        {
            
            e.printStackTrace();
        }
        
    }
}