2015年10月28日 星期三

PHP使用fsockopen() post資料到URL並取得內容

在 php使用curl上傳檔案 中,PHP使用curl去呼叫API,
在ubuntu系統中 curl必須安裝 php5-curl
除了curl還有另一種方法,使用fsockopen()

測試API接口
http://test.localhost/PHP/wget_server.php
echo json_encode($_REQUEST);

stackoverflow的範例
// http://stackoverflow.com/questions/2367458/php-post-data-with-fsockopen
$fp = fsockopen('test.localhost', 80); // 必須寫 test.localhost。不能加protocol、位址和參數 http://test.localhost/PHP/wget_server.php?bear=2

// post的資料
$vars = array(
    'hello' => 'world',
    'bear' => 123
);
$content = http_build_query($vars); // $content:hello=world&bear=123

fputs($fp, "POST /PHP/wget_server.php?bear=2 HTTP/1.1\r\n"); // 位址和參數寫在這,使用POST
fputs($fp, "Host: test.localhost\r\n"); //再寫一次host
fputs($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-Length: ".strlen($content)."\r\n");
fputs($fp, "Connection: close\r\n");
fputs($fp, "Referer: https://www.google.com.tw/?gws_rd=ssl\r\n"); //偽造 $_SERVER['HTTP_REFERER']
fputs($fp, "Cookie: test=456;ai=789\r\n"); // 設定$_COOKIE
fputs($fp, "\r\n");

fputs($fp, $content);

header('Content-type: text/plain'); // 設定Content-Type為純文字
while (!feof($fp)) {
    echo fgets($fp, 1024);
}

fclose($fp);
結果:
HTTP/1.1 200 OK
Date: Wed, 28 Oct 2015 07:16:26 GMT
Server: Apache/2.4.12 (Win32) OpenSSL/1.0.1l PHP/5.6.8
X-Powered-By: PHP/5.6.8
Content-Length: 30
Connection: close
Content-Type: text/html; charset=UTF-8

{"bear":"123","hello":"world"}
$_SERVER['HTTP_REFERER']:https://www.google.com.tw/?gws_rd=ssl
$_COOKIE:Array
(
    [test] => 456
    [ai] => 789
)

說明:
Content-Type,内容类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件
如果未指定 ContentType,默认为TEXT/HTML
指令為text/plain 後,瀏覽器會直接顯示<tag>而不會視為HTML

blog.hsin.tw 的範例
//接收POST參數的URL
$url = "http://test.localhost/PHP/wget_server.php?bear=2";

//POST參數,在這個陣列裡,索引是name,值是value,沒有限定組數
$postdata = array('post_name' => 'post_value', 'acc' => 'hsin', 'nick' => 'joe');

//函式回覆的值就是取得的內容
$result = sendpost($url, $postdata);

echo "\$result:";
print_r($result);

function sendpost($url, $data) {
    
    //先解析url 取得的資訊可以看看http://www.php.net/parse_url
    $url = parse_url($url);
    $url_port = $url['port'] == '' ? (($url['scheme'] == 'https') ? 443 : 80) : $url['port'];
    if (!$url) return "couldn't parse url";
    echo "\$url_port:".$url_port;
    echo "\$url:";
    print_r($url);
    
    //對要傳送的POST參數作處理
    $encoded = "";
    while (list($k, $v) = each($data)) {
        $encoded.= ($encoded ? '&' : '');
        $encoded.= rawurlencode($k) . "=" . rawurlencode($v);
    }

    echo "$encoded:";
    print_r($encoded);
    
    //開啟一個socket
    $fp = fsockopen($url['host'], $url_port);
    if (!$fp) return "Failed to open socket to " . $url['host'];
    
    //header的資訊
    fputs($fp, 'POST ' . $url['path'] . ($url['query'] ? '?' . $url['query'] : '') . " HTTP/1.0\r\n");
    fputs($fp, "Host: " . $url['host'] . "\n");
    fputs($fp, "Content-type: application/x-www-form-urlencoded\n");
    fputs($fp, "Content-length: " . strlen($encoded) . "\n");
    fputs($fp, "Connection: close\n\n");
    fputs($fp, $encoded . "\n");
    
    //取得回應的內容
    $line = fgets($fp, 1024);
    if (!eregi("^HTTP/1.. 200", $line)) return;
    $results = "";
    $inheader = 1;
    while (!feof($fp)) {
        $line = fgets($fp, 2048);
        if ($inheader && ($line == "\n" || $line == "\r\n")) {
            $inheader = 0;
        } 
        elseif (!$inheader) {
            $results.= $line;
        }
    }
    fclose($fp);
    return $results;
}
結果:
$url_port:80
$url:Array
(
    [scheme] => http
    [host] => test.localhost
    [path] => /PHP/wget_server.php
    [query] => bear=2
)
$encoded:post_name=post_value&acc=hsin&nick=joe
$result:{"bear":"2","post_name":"post_value","acc":"hsin","nick":"joe"}

說明:
該網站上的範例有誤,某些換行特殊符號忘記跳脫,如
if($inheader&&($line == "n" || $line == "rn")){
應為
if($inheader&&($line == "\n" || $line == "\r\n")){

\r\n通常是微軟的文件會產生的,在其它的編輯器裡面,會在一行的節尾看到 ^M,那就是\r\n
在php中,字串中的\r\n或\n,要用雙引號才有效。
'\r'是回車,'\n'是換行,前者使光標到行首,後者使光標下移一格。通常用的Enter是兩個加起來

參考資料:
http://blog.hsin.tw/2009/php-post-method-fsockopen/ php 傳送POST到別的URL並取得回應內容 使用fsockopen
http://stackoverflow.com/questions/2367458/php-post-data-with-fsockopen PHP Post data with Fsockopen
http://www.t086.com/code/php/function.php-fsockopen.php 函数:fsockopen()
http://seacatcry.pixnet.net/blog/post/13732061-%E3%80%90%E8%BD%89%E8%B2%BC%E3%80%91%5Cr%5Cn%E5%92%8C%5Cn%E7%9A%84%E5%B7%AE%E7%95%B0 【轉貼】\r\n和\n的差異


2015年10月23日 星期五

PHPExcel 使用心得


如果Excel欄位的數字長度太長(如身分證號、訂單編號)。造成資料儲存問題
http://stackoverflow.com/questions/5753469/problem-reading-numbers-from-excel-with-phpexcel
解法:
ini_set("precision", "20");  // 預設值是12




PHP Array 相加與 Array_merge


例:
$arr_a = array('a'=>1, 'b'=>2, 1=>3);
$arr_b = array('b'=>1, 4, 5);

print_r(array_merge($arr_a, $arr_b));

$arr_c = $arr_a + $arr_b;
print_r($arr_c);
結果:
array_merge($arr_a, $arr_b):Array
(
    [a] => 1
    [b] => 1
    [0] => 3
    [1] => 4
    [2] => 5
)

$arr_c:Array
(
    [a] => 1
    [b] => 2
    [1] => 3
    [0] => 4
)

結論:
array_merge() - key 相同=>後蓋前。沒有 key (流水號 key)的值,則會以附加在尾端 (append) 的方式合併上去,而所有流水號 key 的 index 則會重排
array + array - 有 key 的值的部分是相反的前蓋後,而沒有 key(流水號 key)的部分也會前蓋後,流水號 index 不會重排


參考資料:
http://blog.hsatac.net/2012/11/php-array-plus-array-versus-array-merge/ PHP Array 相加與 Array_merge

2015年10月22日 星期四

在windows和linux使用網路共享資料夾( samba 和cifs),非NFS

因為在 在Windows 7 使用docker  中要配置Virtual Box和windows 7 的共享資料夾失敗
所以改用samba和cifs的方式

在windows上產生共享資料夾
1. 開啟網路和共用中心 => 變更進階共用設定 => 開啟網路探索  /  開啟檔案及印表機共用 => 保存
2. 右鍵你要共享的資料夾 => 內容 => 共用 => 進階共用 => 勾選"共用此資料夾" => 權限 =>
選 Everyone 後下方勾選 "完全控制"
3. 右鍵你要共享的資料夾 => 內容 => 安全性 => 編輯 => 新增 => 輸入物件名稱來選取 填入 Everyone => 確定

在linux上存取windows的共享資料夾
$ sudo apt-get install cifs-utils
$ mkdir ~/windows_shared
掛載
$ sudo mount.cifs //192.168.169.140/test_shared /home/bear/windows_shared -o gid=1000,uid=1000

$ sudo mount -t cifs -o uid=1000,gid=1000 //192.168.169.140/test_shared /home/bear/windows_shared
注意: -o 後面的參數需以逗號(,)分開,不能用空白
查uid 和 gid
$ id
必須指定uid和gid,否則資料夾權限在linux會變成root:root

卸載
$ sudo umount -a -t cifs -l windows_shared/

NFS和samba的差異
In a closed network (where you know every device), NFS is a fine choice.
在封閉網路中( 你知道每個裝置 ),NFS是不錯的選擇
It might look more complicated than it really is but it's solid, predictable and fast. Something you can't level against Samba... At least, in my experience.
NFS比較複雜,但他可靠的、可預見的和快速的。有些事情無法和Samba放在同一層級比較
Samba will probably be a bit slower but is easy to use, and will work with windows clients as well..
Samba 可能會慢一點,但容易使用。並且能與windows端工作

$ sudo apt-get install nfs-kernel-server => 看字面就知道意思 nfs已經算linux模組了
samba其實就是網路芳鄰

在Linux上產生共享資料夾
安裝samba
# sudo apt-get install samba
建立samba的使用者和密碼
# smbpasswd -a bear
建立共享資料夾
$ mkdir ~/ubuntu_shared
編輯設定檔
# vim /etc/samba/smb.conf
格式:
[<folder_name>]
path = /home/<user_name>/<folder_name>
available = yes
valid users = <user_name>
read only = no
browsable = yes
public = yes
writable = yes
Ex.
[ubuntu_shared]
path = /home/bear/ubuntu_shared
available = yes
valid users = bear
read only = no
browsable = yes
public = yes
writable = yes
重啟samba
# service smbd restart

在windows上存取linux的共享資料夾
(假設要建在桌面)在桌面上點右鍵 => 新增 => 捷徑 =>
輸入項目的位置:
\\192.168.169.147\ubuntu_shared

之後便可在sublime 將該捷徑加入project

Windows開分享資料夾給Windows(以前的網路上的芳鄰、網芳)
https://support.microsoft.com/zh-tw/kb/2702421
先在資料夾上點右鍵 => 內容 => 共用 => 共用(按鈕) => 新增Everyone => 共用(按鈕) => 完成
其他電腦嘗試連接該共用資料夾時,出現詢問帳號密碼的對話視窗,若您沒有共用資料夾電腦上的使用者名稱及密碼是無法順利存取該資料夾。
問題的發生原因
這是因為預設是啟用以密碼保護共用,所以共用對象若沒有共用資料夾所在之電腦的使用者帳戶和密碼,即便是開放 Everyone 仍無法存取共用的檔案。
問題的解決方法
控制台 => 網路和網際網路 => 網路和共用中心 => 變更進階共用設定 => ( 家用或工作場所 => 以密碼保護的共用 )選取"關閉以密碼保護的共用"
取消共用
資料夾上點右鍵 => 內容 => 共用 => 進階共用(按鈕) => 取消勾選"共用此資料夾"

參考資料:
http://www.howtogeek.com/176471/how-to-share-files-between-windows-and-linux/ How to Share Files Between Windows and Linux
http://stackoverflow.com/questions/74626/how-do-you-force-a-cifs-connection-to-unmount  How do you force a CIFS connection to unmount
http://askubuntu.com/questions/7117/which-to-use-nfs-or-samba  Which to use NFS or Samba?









PHP和javascript 的 hex、byte陣列、string轉換

某次使用SlowAES  函數cryptoHelpers.generateSharedKey(8)產生的iv經過base64加密後的結果如下
R1We4y0JRP5w06Z8tUBPAw==

先看 https://code.google.com/p/slowaes/source/browse/trunk/js/cryptoHelpers.js?r=33 的 generateSharedKey 怎麼產生的
generateSharedKey:function(len)
{
 if(len === null)
  len = 16;
 var key = [];
 for(var i = 0; i < len*2; i++)
  key.push(this.getRandom(0,255));
 return key;
}
產生長度為8*2 ,內容為 0-255 的 byte 陣列

使用 cryptoHelpers.js 對他做處理,觀察各個型態的內容
// need include cryptoHelpers.js
var base64 = 'R1We4y0JRP5w06Z8tUBPAw==';
console.log(cryptoHelpers.base64.decode(base64)); // base64 decode => byte array
console.log(cryptoHelpers.convertByteArrayToString(cryptoHelpers.base64.decode(base64))); // to string
console.log(cryptoHelpers.toHex(cryptoHelpers.base64.decode(base64))); // to hex

結果:
cryptoHelpers.base64.decode(base64):[71, 85, 158, 227, 45, 9, 68, 254, 112, 211, 166, 124, 181, 64, 79, 3]
cryptoHelpers.convertByteArrayToString(cryptoHelpers.base64.decode(base64)):GUžã- DþpÓ¦|µ@O
cryptoHelpers.toHex(cryptoHelpers.base64.decode(base64)):47559ee32d0944fe70d3a67cb5404f03

使用PHP處理
$iv = 'R1We4y0JRP5w06Z8tUBPAw==';

echo "
base64_decode(\$iv):".base64_decode($iv); // base64 decode => binary string

$iv64 = base64_decode($iv);
echo "
strlen(\$iv64):".strlen($iv64);  // 長度16
echo "
pack('H*', bin2hex(\$iv64)):".pack('H*', bin2hex($iv64)); // 與base64_decode($iv)結果相同
echo "
bin2hex(\$iv64):".bin2hex($iv64); // to hex

// to bytes array
for ($i=0; $i < strlen($iv64); $i++) { 
    $data[] = ord(substr($iv64,$i,1)); // 使用ord將字元轉成int
}
echo "
\$data:";
print_r($data);
echo "
";

結果:
base64_decode($iv):GU��- D�pӦ|�@O
strlen($iv64):16
pack('H*', bin2hex($iv64)):GU��- D�pӦ|�@O
bin2hex($iv64):47559ee32d0944fe70d3a67cb5404f03
$data:Array
(
    [0] => 71
    [1] => 85
    [2] => 158
    [3] => 227
    [4] => 45
    [5] => 9
    [6] => 68
    [7] => 254
    [8] => 112
    [9] => 211
    [10] => 166
    [11] => 124
    [12] => 181
    [13] => 64
    [14] => 79
    [15] => 3
)

還可以去 Unicode/UTF-8-character table 做字元最後的檢查
http://dev.networkerror.org/utf8/?start=0&end=255&cols=4&search=&show_uni_int=on&show_uni_hex=on&show_html_ent=on&show_raw_hex=on&show_raw_bin=on  0-255 的 Unicode Number (int) / Unicode Number (hex) / Char
http://www.scarfboy.com/coding/unicode-tool?s=000047  以hex 搜尋字元

參考資料:
http://stackoverflow.com/questions/11044802/php-hex-and-binary PHP Hex and Binary






2015年10月20日 星期二

在Windows 7 使用docker

下載安裝 docker-install.exe , 如遇上這個錯誤:
https://github.com/boot2docker/windows-installer/issues/67
則移除VirtualBox 後重裝 VirtualBox 4.3.12 版本,重啟boot2docker-vm

中間為了讓virtualbox 與Win7共用資料夾 ( 失敗 )
http://askubuntu.com/questions/456400/why-cant-i-access-a-shared-folder-from-within-my-virtualbox-machine
必須從 C:\Program Files\Oracle\VirtualBox\VBoxGuestAdditions.iso 安裝 Guest Additions
在開機前先設定掛載的光碟
然後設定共享文件夾
開機進入VM後
1. I had to manually mount the CD:
$ sudo mount /dev/cdrom /media/cdrom
掛載之後 /media/cdrom 下面就有光碟內的東西了
/media/cdrom# ls
32Bit  AUTORUN.INF  cert  runasroot.sh            VBoxSolarisAdditions.pkg        VBoxWindowsAdditions.exe
64Bit  autorun.sh   OS2   VBoxLinuxAdditions.run  VBoxWindowsAdditions-amd64.exe  VBoxWindowsAdditions-x86.exe

2. Install the necessary packages:
$ sudo apt-get install make gcc linux-headers-$(uname -r)
3. Install the Guest Additions:
$ sudo /media/cdrom/VBoxLinuxAdditions.run
中間報錯
...
The headers for the current running kernel were not found. If the following
module compilation fails then this could be the reason.

Building the main Guest Additions module ...done.
Building the shared folder support module ...fail!
(Look at /var/log/vboxadd-install.log to find out what went wrong)
...
Setting up the Window System to use the Guest Additions ...done.
You may need to restart the hal service and the Window System (or just restart
the guest system) to enable the Guest Additions.

Installing graphics libraries and desktop services components ...done.

嘗試mount共享資料夾
$ mkdir new
$ sudo mount -t vboxsf New ~/new
/sbin/mount.vboxsf: mounting failed with the error: No such device

嘗試用其他方法解
http://stackoverflow.com/questions/28328775/virtualbox-mount-vboxsf-mounting-failed-with-the-error-no-such-device
# cd /opt/VBoxGuestAdditions-4.3.12/init/
# ./vboxadd setup
與跑 VBoxLinuxAdditions.run 一樣的錯誤

嘗試解決 Error: kernel headers not found.的問題
http://askubuntu.com/questions/98416/error-kernel-headers-not-found-but-they-are-in-place
# apt-get install xserver-xorg xserver-xorg-core
安裝了以下東西
The following NEW packages will be installed:
  libmtdev1 libxi6 libxinerama1 xserver-xorg xserver-xorg-input-all
  xserver-xorg-input-evdev xserver-xorg-input-mouse
  xserver-xorg-input-synaptics xserver-xorg-input-vmmouse
  xserver-xorg-input-wacom
0 upgraded, 10 newly installed, 0 to remove and 1 not upgraded.

嘗試解決The headers for the current running kernel were not found.
http://linuxconfig.org/ubuntu-the-headers-for-the-current-running-kernel-were-not-found-solution
# apt-get install linux-headers-`uname -r` dkms build-essential
報錯:
...
The following packages have unmet dependencies:
 build-essential : Depends: libc6-dev but it is not going to be installed or
                            libc-dev
                   Depends: g++ (>= 4:4.4.3) but it is not going to be installed
E: Unable to correct problems, you have held broken packages.

原因:
# apt-get install libc6-dev
...
libc6-dev : Depends: libc6 (= 2.19-0ubuntu6) but 2.19-0ubuntu6.5 is to be installed
E: Unable to correct problems, you have held broken packages.
# apt-show-versions libc6
libc6:amd64 2.19-0ubuntu6.5 newer than version in archive
看來是libc6 太新了
切勿移除libc6重裝,剛剛VM就這樣被我弄掛的。還好昨天剛好有備份

嘗試更新Virtual Box到最新版 4.3.30
更新到最新版要執行原本的虛擬電腦時報錯:
unable to load r3 module vboxdd.dll (vboxdd) getlasterror=1790 (verr_unresolved_error)
嘗試過這篇 http://blog.sina.com.cn/s/blog_4dc988240102vj8a.html 但無效
只好把Virtual Box再還原回4.3.12版


PHP 顯示syntax error


如果在寫php時忘記加分號( Semicolon ),訪問下面這個頁面會得到空白頁
ini_set("display_errors", "1");
error_reporting(E_ALL);
$bear = array(1,2,3)
如何顯示錯誤呢?
error_setting.php
ini_set("display_errors", "1");
error_reporting(E_ALL);
include 'error.php';
error.php
$bear = array(1,2,3)
echo "test";
訪問error_setting.php會得到錯誤訊息:
Parse error: syntax error, unexpected 'echo' (T_ECHO) in /var/www/html/test.vm/error.php on line 
但直接訪問error.php仍然會得到空白頁

20160519 找到原因
修改 /etc/php5/fpm/php.ini
error_reporting = E_ALL
error_reporting = E_ALL & ~E_NOTICE & ~E_STRICT #忽略notice和Strict Standards錯誤,但會報Fatal錯誤
syntaxfatal 的error log 與  /etc/php5/fpm/php-fpm.conf 無關,與nginx /etc/nginx/sites-available/test.vm 的 error_log /var/www/html/test.vm/logs/error.log; 有關
這樣畫面上和error log 都有錯誤出現
http://stackoverflow.com/questions/1911920/does-php-error-reporting0-affect-error-logging-or-just-display
結論:看來沒辦法只顯fatal和syntax和notice示錯誤在error log,除非用error_log()或用框架

參考資料:
http://stackoverflow.com/questions/16933606/error-reportinge-all-does-not-produce-error  error_reporting(E_ALL) does not produce error




2015年10月14日 星期三

AES 加密心得

前言:要跟java那邊用AES加密後的資料對接

在網路上找到許多不同語言的實作方式,但加密出來的結果都不一樣

原來是 AES encryption 有以下幾種mode

● ECB should not be used if encrypting more than one block of data with the same key.
當使用相同key加密一個block以上的資料時,ECB不應該被使用
● CBC, OFB and CFB are similar, however OFB/CFB is better because you only need encryption and not decryption, which can save code space.
CBC, OFB 和CFB類似。OFB/CFB比較好,因為你只需要加密不需要解密,以減少code的量
● CTR is used if you want good parallelization (ie. speed), instead of CBC/OFB/CFB.
當你想要好的平行處理(如:速度),使用CTR。而不是CBC/OFB/CFB。
The most important caveat with CTR mode is that you never, ever re-use the same counter value with the same key. If you do so, you have effectively given away your plaintext. ( http://stackoverflow.com/questions/4951468/ctr-mode-use-of-initial-vectoriv CTR mode use of Initial Vector(IV) )
最重要的是,你不要重複使用相同的key(IV,隨機產生),如果你這樣做,你已有效地給了你的明文
● XTS mode is the most common if you are encoding a random accessible data (like a hard disk or RAM).
XTS用在硬碟和RAM上
● OCB is by far the best mode, as it allows encryption and authentication in a single pass. However there are patents on it in USA.
OCB最好,因為他允許加密和認證在單通道。然而美國擁有其專利。( 所以意味著你在網路上找不到實作他的code )

你必須每次都用獨特的IV去加密,如果你不能保證他的隨機性,請用只需要隨機數(非IV)的OCB。固定的IV使得人們能不斷的猜測下一個,隨機數能避免這個風險

初始向量 Initialization vector (IV) 可被公開
http://ijecorp.blogspot.com/2013/08/python-m2crypto-aes-encrypt-decrypt.html
IV 本身並不需要保護,它是可以被公開的。而IV的最大長度必須是 16 bytes,而且產生IV的方式必須是無法預測的,也就是隨機產生即可。
http://stackoverflow.com/questions/8804574/aes-encryption-how-to-transport-iv
There is no security hole by sending the IV in clear text - this is similar to storing the salt for a hash in clear: As long as the attacker has no controll over the IV/salt, and as long as it is random, there is nor problem.
用明文傳送IV沒有安全的漏洞。這就像你做hash加了salt一樣,只要攻擊者無法掌握IV(salt)並且他是隨機的,就不會有問題。

使用php做AES CBC 128 pkcs5padding加密
$value = "张根";
$key = "Bar12345Bar12345"; //16 Character Key

echo strToHex('张根'); // hex: e5bca0e6a0b9

$encrypted = getEncrypt($value, $key);
echo "\n\$encrypted:".$encrypted;
echo "\n\getDecrypt(\$encrypted, \$key):".getDecrypt($encrypted, $key);

function pkcs5_pad ($text, $blocksize) { // https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php
 $pad = $blocksize - (strlen($text) % $blocksize); 
 return $text . str_repeat(chr($pad), $pad); 
} 

function getEncrypt($sStr, $sKey) { // http://stackoverflow.com/questions/4537099/problem-with-aes-256-between-java-and-php
 global $iv;
 $sStr = pkcs5_pad($sStr, 16); // 這個16是 mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC) 的結果
 echo "\n\$sStr:".$sStr;  // 測試pkcs5 padding的結果
 // 產生$iv,如果用class寫,可以避免全域變數
 // $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB), MCRYPT_RAND);
 // echo "\n\$iv:".$iv; // 這是隨機產生的內容
  return base64_encode( // 用bin2hex()亦可,但解密時要用hex2bin()
    mcrypt_encrypt(
        MCRYPT_RIJNDAEL_128, 
        $sKey,
        $sStr,
        MCRYPT_MODE_CBC,
        "ThisIsASecretKet" // $iv,測試時寫死
    )
  );
}

function getDecrypt($sStr, $sKey) {
 global $iv; // 要與 getEncrypt()產生的$iv一致,才能解出來
  return mcrypt_decrypt(
    MCRYPT_RIJNDAEL_128, 
    $sKey, 
    base64_decode($sStr), // 加密時用bin2hex(),則解密時要用hex2bin()
    MCRYPT_MODE_CBC,
    "ThisIsASecretKet"
  );
}

function strToHex($string) // http://ditio.net/2008/11/04/php-string-to-hex-and-hex-to-string-functions/
{
    $hex='';
    for ($i=0; $i < strlen($string); $i++)
    {
        $hex .= dechex(ord($string[$i]));
    }
    return $hex;
}

結果:
e5bca0e6a0b9 //這邊是utf8中文字轉hex的結果,如果其他地方字串轉hex不是這個代表他們的字串原本編碼不是utf-8
$sStr:张根 // pkcs5 padding的結果
$encrypted:LE/jvtjPWJk7qJc49Xl3eQ== // aes加密後 base64_decode()的結果
\getDecrypt($encrypted, $key):张根 // aes解密結果

說明:
因為java那邊只能用 128bit,所以只能選 MCRYPT_RIJNDAEL_128
$iv = Initial Vector(IV) 初始向量
在 https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php 範例中,我們可以看到他加密不是用 mcrypt_encrypt 而是
public static function encrypt($input, $key) {
    $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB);
    $input = Security::pkcs5_pad($input, $size);
    $td = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_ECB, '');
    $iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
    mcrypt_generic_init($td, $key, $iv);
    $data = mcrypt_generic($td, $input);
    mcrypt_generic_deinit($td);
    mcrypt_module_close($td);
    $data = base64_encode($data);
    return $data;
}
mcrypt_generic() => 低階API ,更有彈性
mcrypt_encrypt() => 高階工具( higher-level utility )
參考資料:
http://stackoverflow.com/questions/2773535/mcrypt-generic-vs-mcrypt-encrypt mcrypt_generic vs mcrypt_encrypt

http://php.net/manual/en/function.mcrypt-encrypt.php
string mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] )
mode的格式是 MCRYPT_MODE_modename ,modename可使用"ecb", "cbc", "cfb", "ofb", "nofb" or "stream"
如: MCRYPT_MODE_ECB

因為mcrypt_encrypt() 出來的結果打印是二進位亂碼,所以都用 bin2hex()或base64_encode()去轉換一次

報錯:
Warning: mcrypt_encrypt(): Key of size 15 not supported by this algorithm. Only keys of sizes 16, 24 or 32 supported
如果你出現這個錯誤,請把$key補到16位(或24, 32位)
$key=$key."\0"; //缺幾位就補幾個


PHP範例參考資料:
https://github.com/stevenholder/PHP-Java-AES-Encrypt/blob/master/security.php 使用pkcs5_pad()方法
http://stackoverflow.com/questions/4537099/problem-with-aes-256-between-java-and-php  getEncrypt($sStr, $sKey)和getDecrypt($sStr, $sKey) 原型

使用Java做AES CBC 128 pkcs5padding加密
import java.io.UnsupportedEncodingException;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key1, String key2, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(key2.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key1.getBytes("UTF-8"),
                    "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string:"
                    + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key1, String key2, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(key2.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key1.getBytes("UTF-8"),
                    "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws UnsupportedEncodingException {
        String key1 = "Bar12345Bar12345"; // 128 bit key
        String key2 = "ThisIsASecretKet";
        System.out.println(decrypt(key1, key2,
                encrypt(key1, key2, new String("张根".getBytes("utf-8")))));
        System.out.println(parseByte2HexStr("张根".getBytes("utf-8"))); // print "张根" utf-8 in hex
    }
    
    /**
     * 将16进制转换为二进制
     * 
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    /**
     * 将二进制转换成16进制
     * 
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toLowerCase());
        }
        return sb.toString();
    }
}

結果:
encrypted string:LE/jvtjPWJk7qJc49Xl3eQ== // aes加密後 base64_decode()的結果,需與php結果一致
张根 // aes 解密結果
e5bca0e6a0b9 // "张根"utf8轉hex的結果

說明:
parseHexStr2Byte(String hexStr) 和 parseByte2HexStr(byte buf[]) 這兩個function在這邊純粹測試用,與加解密無關。可忽略
我遇到的最大難點之一就是,java和php 英文字加密出來結果一樣,但是中文加密結果不一樣。( 這邊23樓也遇到一樣問題: http://my.oschina.net/Jacker/blog/86383?p=3#comments  )
原因是java在加密前的中文編碼有問題。先用
System.out.println(parseByte2HexStr("张根".getBytes("utf-8")));
檢測hex是否相同
加密時在外面就轉成utf-8再加密
encrypt(key1, key2, new String("张根".getBytes("utf-8"))) 

如果你的cipher(密文),想要用No Padding,如
Cipher cipher = Cipher.getInstance("AES/CBC/NoPADDING");
這樣你要加密的明文( "张根" ) 必須改成16位的字,否則java會報錯
encrypt(key1, key2, new String("123456789012345".getBytes("utf-8"))) // 必須改成16位字串,如1234567890123456
報錯:
javax.crypto.IllegalBlockSizeException: Input length not multiple of 16 bytes

雖然php不做padding可以加密,但結果不一樣。為了配合java這邊,php那邊還是要做pkcs5 padding

原因:
java文件檔的編碼不是utf-8
解法:
eclipse => "左邊導航欄"你的"專案"上點右鍵 => Properties => Resource => Text file encoding 選
Other: UTF-8 ( 不要選Inherited from container (GBK) ) => OK ( 這樣你原本GBK的文件中文內容會變成亂碼,代表原本編碼錯誤 )


JAVA範例參考資料:
http://stackoverflow.com/questions/15554296/simple-java-aes-encrypt-decrypt-example Simple java AES encrypt/decrypt example

使用javascript ( SlowAES )做AES CBC 128 pkcs5padding加密
<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="description" content="aes">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>aes</title>
        <script src="/jquery-1.10.2.min.js"></script>
        <script src="../js/aes.js"></script>
        <script src="../js/cryptoHelpers.js"></script>
        <script src="../js/jsHash.js"></script>
    </head>
    <body>
        <div id="output"></div>
        <script type="text/javascript">
        /**
        * An encryption setup to match our server-side one; see there for
        * documentation on it.
        **/
        function decrypt(input, key){
            var originalSize = 6;
            // var iv = 'R1We4y0JRP5w06Z8tUBPAw==';
            // iv = cryptoHelpers.base64.decode(iv); // 解密時需把base64加密後的iv解密成byte array
            var iv = "ThisIsASecretKet";
            iv = cryptoHelpers.convertStringToByteArray(iv);
            var cipherIn = input;
            // Set up encryption parameters
            var keyAsNumbers = cryptoHelpers.toNumbers( bin2hex( key ) );
            cipherIn = cryptoHelpers.base64.decode(cipherIn);
            var decrypted = slowAES.decrypt(
                cipherIn,
                slowAES.modeOfOperation.CBC,
                keyAsNumbers,
                iv
            );
            return cryptoHelpers.decode_utf8(cryptoHelpers.convertByteArrayToString(decrypted));
        }
        function encrypt( plaintext, key ){
            // Set up encryption parameters
            plaintext = cryptoHelpers.encode_utf8(plaintext);
            var inputData = cryptoHelpers.convertStringToByteArray(plaintext);
            var keyAsNumber = cryptoHelpers.toNumbers(bin2hex(key));
            var iv = cryptoHelpers.generateSharedKey(8); // 假設自動生成的iv做base64 encode加密後的結果是 R1We4y0JRP5w06Z8tUBPAw==
            var iv = "ThisIsASecretKet";
            iv = cryptoHelpers.convertStringToByteArray(iv);
            var encrypted = slowAES.encrypt(
                inputData,
                slowAES.modeOfOperation.CBC,
                keyAsNumber,
                iv
            );
            return cryptoHelpers.base64.encode(encrypted);
        }
        // Equivilent to PHP bin2hex
        function bin2hex (s) {
            var i, f = 0,
                a = [];
            s += '';
            f = s.length;
            for (i = 0; i < f; i++) {
                a[i] = s.charCodeAt(i).toString(16).replace(/^([\da-f])$/, "0$1");
            }
            return a.join('');
        }
        // Equivilent to PHP hex2bin
        function hex2bin(hex) {
            var str = '';
            for (var i = 0; i < hex.length; i += 2)
                str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
            return str;
        }
        /**
        * Some simple testing code
        **/
        $(function(){
            var key = "Bar12345Bar12345"; // key
            var plaintext = "张根";
            var output = "";
            var cipherText = encrypt(plaintext,key);
            var newPlaintext = decrypt(cipherText,key);
            output += ("<br>plaintext=" + plaintext);
            output += ("<br>cipherText=" + cipherText);
            output += ("<br>newPlaintext=" + newPlaintext);
            $('#output').html(output);
        });
        </script>
    </body>
</html>
結果:
plaintext=张根
cipherText=LE/jvtjPWJk7qJc49Xl3eQ==
newPlaintext=张根

如果要傳做過base64加密後的iv給php端,php端的iv要這樣設定,才能解密
$aes->set_iv(base64_decode($iv));

SlowAES的aes.js、cryptoHelpers.js、jsHash.js
https://code.google.com/p/slowaes/source/browse/trunk/js/

加密出來的結果要傳送
POST
1. 塞入表單後submit POST
GET
1. 塞入表單後submit GET
2. 組URL
url = $('#action').val()+"&aes_encrypt="+encodeURIComponent($('#reqParam').val())+"&iv="+$('#iv').val();
location.href = url;
必須用在字段上使用 encodeURIComponent 。
1. 勿組出url後再encodeURIComponent(url), 因為http:// 也會被encode
2. 使用encodeURI無效


其他java或php實作AES範例:
http://www.movable-type.co.uk/scripts/aes-php.html  Aes Ctr <PHP>
http://www.movable-type.co.uk/scripts/aes.html Aes Ctr <javascript> => github: https://github.com/chrisveness/crypto
http://aesencryption.net/ AES encryption <PHP/JAVA> =>Java驗證未過,可能是當初測時編碼問題
https://code.google.com/p/crypto-js/#AES crypto-js<javascript>
http://point-at-infinity.org/jsaes/ jsaes: AES in JavaScript <javascript>
http://www.cnblogs.com/yipu/articles/3871576.html [转]php与java通用AES加密解密算法 (最初對接成功的範例,但有java中文編碼問題) <PHP/JAVA>
https://github.com/stevenholder/PHP-Java-AES-Encrypt  PHP-Java-AES-Encrypt<PHP/JAVA>
http://www.java2s.com/Code/Java/Security/BasicIOexamplewithCTRusingAES.htm Basic IO example with CTR using AES : File Secure IO « Security « Java <JAVA>
http://magiclen.org/aes/ 在Java、Android、PHP實現AES加解密,並且互通的方式 <PHP/JAVA>

參考資料:
https://zh.wikipedia.org/wiki/%E9%AB%98%E7%BA%A7%E5%8A%A0%E5%AF%86%E6%A0%87%E5%87%86 高階加密標準
http://stackoverflow.com/questions/1220751/how-to-choose-an-aes-encryption-mode-cbc-ecb-ctr-ocb-cfb  How to choose an AES encryption mode (CBC ECB CTR OCB CFB)?







2015年10月7日 星期三

升級XAMPP的phpmyadmin

XAMPP v3.2.1 的phpmyadmin有個bug
搜尋出結果後,放著過一陣子會重刷那一頁,導致之前的SQL和結果不見

所以 先去 phpmyadmin 官網 下載最新版,我解壓縮到 E:\www 下面
然後將 file:C:/xampp/phpMyAdmin/config.inc.php 複製到 E:/www/phpMyAdmin-4.5.0.2-all-languages/ 下面

設定Alias 
因為XAMPP 的 file:C:/xampp/apache/conf/extra/httpd-xampp.conf 裡面含有這行設定
Alias /phpmyadmin "C:/xampp/phpMyAdmin/"

所以我就 在file:C:/xampp/apache/conf/extra/httpd-vhosts.conf 中新增這個設定
Alias /phpmyadmin45 E:\www\phpMyAdmin-4.5.0.2-all-languages

http://localhost/phpmyadmin/ => 訪問XAMPP舊版phpmyadmin ( 4.3.11 )
http://localhost/phpmyadmin45/ => 訪問新版的phpmyadmin (  4.5.0.2 )

但是新版phpmyadmin有個bug,就是左邊資料庫搜尋資料表的 navigation 不會叫Ajax ( http://localhost/phpmyadmin/navigation.php?ajax_request=1&server=3&token=aaa31d2b7f2f8c54544092194e1e9fe8 ) 去搜尋所有資料表名字,只會搜尋該頁的

解法:
在 file:E:/www/phpMyAdmin-4.5.0.2-all-languages/config.inc.php 中 加入
$cfg['MaxNavigationItems'] = 200; // 設一個較大的數字,不讓他分頁,就能搜尋所有資料表了
phpmyadmin首頁 => 設置 => 導航面板 => 节点中最大项数( The number of items that can be displayed on each page of the navigation tree. )
註: 
第二種設法 是把該設定值存到 phpmyadmin資料庫 的 pma__userconfig.config_data 欄位
ex. 
{"MaxNavigationItems":210,"Server\/hide_db":"","Server\/only_db":"","lang":"zh_CN","collation_connection":"utf8mb4_unicode_ci"}