2016年1月27日 星期三

RSA, AES加密大檔案

RSA加密有長度限制,測試了一下最大是245K Byte,超過就會報錯「data too large for key size」
如果改用AES則沒有長度限制,但AES是對稱式的加密,等於你要把密鑰傳給對方,對方才能解密。這樣子中間轉手的Router,或是同網域中的電腦就能接收到到你的密鑰了。
那麼解決方法是什麼呢?
答:將資料用AES加密,AES加密時的密鑰用RSA加密,然後一起傳給對方就行了

那怎麼實作呢?Linux上面有openssl所有的功能都在裡面了,不管是要編碼,要加密解密,通通都有了

2015年3月12日 星期四

Github Pages 使用jekyll安裝與設定,及連結多個github專案

第一個github pages

在github建立一個repository 名稱為<username>.github.io

好了,剛建立好等一陣子,打開瀏覽器進入<username>.github.io,就可以看到空白網站了

<username>.github.io git clone下來,隨便加個index.html就可以看到結果了

連結其他的github專案

每個github專案都可以自己管理自己的github pages,不用都放在剛剛那個<username>.github.io裡面。

譬如你還有一個github專案,名稱為project,則在project裡面再開啟一條branch命名為gh-pages,那麼連結<username>.github.io/project,就可以連到project裡面了。

只是gh-pages這條分支,都只會用來做專案的網頁呈現,所以我們會將它設定為獨立分支,不從master裡面分出來,所以開啟分支時,使用

git checkout --orphan gh-pages

就可以開啟獨立的分支,沒有任何parent的分支,這時候再把原本的檔案都刪掉,這條分支就專心做網頁就好。

使用jekyll

從HTML慢慢刻出一個網頁有點累,github pages大多是用jekyll這個框架來產生部落格網站。

怎麼安裝,jekyll-quick-start 已經講得很清楚了,這邊說一下,連結其他github專案時要特別設定的地方

在jekyll根目錄有個_config.yml檔案,找到BASE_PATH :這一行。如果沒有修改的話,在網頁裡面預設的連結都會連回<username>.github.io,而不是你目前的<username>.github.io/project,這裡使用絕對路徑或相對路徑都可以

絕對路徑

BASE_PATH : <username>.github.io/project

相對路徑

BASE_PATH : /project

這樣子預設的連結就不會亂跑了,我一開始找不到,只好傻傻的去改模板,越改越覺得不對勁,不可能要使用者用這麼智障的方法,仔細再看一次_config.yml檔,才發現可以設定

2015年1月20日 星期二

Ruby, Product Key, 使用Ruby自製產品序號,獎品序號

製作產品序號要多複雜可以搞得多複雜,所以這篇文章以最簡單的AES加密,並用BASE32編碼為英數字為例。原理相同,之後想搞得多複雜可以慢慢做

關於AES,與Base32。我將使用以下步驟來產生產品序號

  1. 規劃要產生的文字,譬如客戶輸入序號,得到獎品為紅藥水10瓶,就簡單的使用potion10這串文字來編成序號。
  2. 使用AES編碼產生類似U\xDB\e\xDC\xA2S$\xE7p\x13\x85\xD3'\x00\x9B\x10encryption block
  3. 使用Base32將上面那堆醜醜的東西,編碼為英文與數字的組合,類似KXNRXXFCKMSOO4ATQXJSOAE3CA======

然後就可以將KXNRXXFCKMSOO4ATQXJSOAE3CA======送給你的客戶,當他輸入獎品序號後,在解碼回來就行了。後面那個====,可以不用給客戶,你的後台收到客戶輸入時,幫他加上去就行了。

解碼回來得到字串potion10就給那位客戶10罐紅藥水,若解碼回來的字串沒有意義,就代表那是隨意輸入的序號,紀錄他嘗試錯誤的次數在log,要注意有人在亂try。

至於=符號的數量代表什麼意思,可以參考這篇文章Base32 Encoding Algorithm

中間有一段

The Base32 encoding process is to:

Divid the input bytes stream into blocks of 5 bytes.

Divid 40 bits of each 5-byte block into 8 groups of 5 bits.

Map each group of 5 bits to 1 printable character, based on the 5-bit value using the Base32 character set map.

If the last 5-byte block has only 1 byte of input data, pad 4 bytes of zero (\x0000). After encoding it as a normal block, override the last 6 characters with 6 equal signs (======).

If the last 5-byte block has only 2 bytes of input data, pad 3 bytes of zero (\x0000). After encoding it as a normal block, override the last 4 characters with 4 equal signs (====).

If the last 5-byte block has only 3 bytes of input data, pad 2 bytes of zero (\x0000). After encoding it as a normal block, override the last 3 characters with 3 equal signs (===).

If the last 5-byte block has only 4 bytes of input data, pad 1 byte of zero (\x0000). After encoding it as a normal block, override the last 1 characters with 1 equal sign (=).

Carriage return (\r) and new line (\n) are inserted into the output character stream. They will be ignored by the decoding process.

根據我的測試,只要選用相同的AES加密模式(譬如我下面會用128 bit, CBC模式),產生的=號數都相同,所以可以當作是常數,直接寫在程式碼裡面,BASE32編碼後拿掉,解碼後加回來,就可以了。

我以Ruby來實作,Ruby本身已經實做了OpenSSL相關功能,而Base32的實作,可以參考Base32

以下是我已經在電腦安裝Base32的情況下,來實作序號這個功能(若還沒安裝可以gem install --remote base32來安裝)

require 'openssl'
require 'base32'

data = 'potion10'   

cipher = OpenSSL::Cipher::AES128.new(:CBC)
cipher.encrypt
key = cipher.random_key
iv = cipher.random_iv
encrypted = cipher.update(data) + cipher.final

encrypted_base32 = Base32.encode(encrypted)
#=> "N4AGAVVX34E3W6KSANFVGYG4IM======"
#   我們取"N4AGAVVX34E3W6KSANFVGYG4IM"這個部分給客戶輸入就好
#   解密時,把後面5個'='號給補上就好
#   如果你選用的key, iv產生的'='數目不同,則自行改變

解密的話,就把步驟反過來,先用Base32解碼,再解密,不過要用同一組key跟iv(關於iv是什麼,AES加解密過程中使用的一個陣列,詳細請看塊密碼的工作模式)

假設剛剛加密時,使用的key是U\x94\xCA[\x00\xD1\x06\x81\x8C\xA7eU<\xAC\x16\xC9,使用iv是\x03\xFA\xBDe\x81$\xD1\xF20\x18%[lA\xC7\x15

通常這些資料會寫在環境變數裡面,然後在ruby裡面使用ENV[key],ENV[iv]取出來比較安全。就像Rails裡面secrets.yml檔案讀取<%= ENV["SECRET_KEY_BASE"] %>相同的道理,詳參閱config/secrets.yml

所以我們先把key與iv放在環境變數裡面,在console裡面打上

AES128_CBC_KEY='U\x94\xCA[\x00\xD1\x06\x81\x8C\xA7eU<\xAC\x16\xC9'
AES128_CBC_IV='\x03\xFA\xBDe\x81$\xD1\xF20\x18%[lA\xC7\x15'
export AES128_CBC_KEY
export AES128_CBC_IV

AES128_CBC_KEYAES128_CBC_IV設定好,並export為環境變數

因此,最上面那個加密並編碼的程式就可以改成這樣,我們直接寫成Method的形勢,接受的參數是要加密的序號

require 'openssl'
require 'base32'

def encrypt_to_serial data

    cipher = OpenSSL::Cipher::AES128.new(:CBC)
    cipher.encrypt

    # 差別在此,直接從環境變數取得key與iv
    # 之後解密時,也直接從環境變數取得key與iv
    cipher.key = ENV['AES128_CBC_KEY']
    cipher.iv = ENV['AES128_CBC_IV']

    encrypted = cipher.update(data) + cipher.final
    encrypted_base32 = Base32.encode(encrypted)

    # 本例所選AES模式 編碼後,後面會有6個等號,所以回傳[0..-7]
    return encrypted_base32[0..-7]
end

這樣子,我們的的解密程式,也就可以簡單的依序反向操作,也不用擔心kev與iv要如何取得的問題

require 'openssl'
require 'base32'

def decrypt_from serial

    # 同樣的 本例是因為後面要補回6個'='號
    encrypted_base32 = serial << "======"
    encrypted = Base32.decode(encrypted_base32)

    decipher = OpenSSL::Cipher::AES128.new(:CBC)
    decipher.decrypt
    decipher.key = ENV['AES128_CBC_KEY']
    decipher.iv = ENV['AES128_CBC_IV']

    return decipher.update(encrypted) + decipher.final
end

以上做法是比較簡單明顯的做法,實務上可以考慮寫成class來使用

2014年12月12日 星期五

linux, bash, find 的應用(-exec, sed -i, 檔案內取代, 與xargs比較)

find -exec

find -exec 指的是將找到的檔案,送到後面的指令去處理。從-exec\;為止,代表是接受從find送來要處理的指令,而送來的檔案將用{}代表找到的檔案。

譬如你想把所有副檔名為.log的檔案刪除掉,可以這麼做

find . -type f -name "*.log" -exec rm {} \;

(其實我加上-type f有點多餘,因為我已經指定-name "*.log"了,就不可能輸出資料夾了。)

如果刪檔案的時候,一直要你按yes,可以這樣

yes | find . -type f -name "*.log" -exec rm {} \;

yes這個指令會一直串流輸出yes,這樣刪檔案就自動一直輸入yes

例子中find找到的檔案,就放到-exec\;之間的指令去處理,譬如找到了檔案develop.log,就會變成

rm develop.log

另外,{}並不是規定只能出現一次,譬如你要將資料夾內所有檔案加上副檔名log,可以這麼做

find . -type f -exec mv {} {}.log \;

這指令會包含子資料前內的檔案都加上.log副檔名。如果只想要目前資料夾的檔案,所以可以加上-maxdepth 1,若要地回到下一層資料夾可以將1改成2,3或4以此類推

find . -maxdepth 1 -type f -exec mv {} {}.log \;

因此,若要批次改變檔案的內容,就可以搭配find -execsed -i。sed 加上 -i 參數,代表直接對檔案內容做修改。

我常常在幫別人複製或移動網站,很多人的網址都寫成包含域名的絕對路徑,所以常常要用這個指令去找出所有含舊域名的檔案,並改成新域名。

譬如舊域名為http://www.old.com,要改成http://www.new.com,我會這麼做

find . -type f -exec sed -i 's/www.old.com/www.new.com/g' {} \;

(sed這個指令在OS X上面如果照上例那樣執行,會有錯誤訊息。詳細請參考stackoverflow上面的討論)

這樣子會找遍所有的檔案,需要的時間有點久。我會再加上參數-not -name "不需要的檔名",來排除一些不需要尋找的檔案

find . -type f -not -name "*.log" -not -name "*.jpg" -exec sed -i 's/www.old.com/www.new.com/g' {} \;

像這樣跳過所有的log檔與jpg圖檔,有需要可以加上其他如-not -name "*.png",才加快速度。或者乾脆指定-name "*html"-name "*.php",只尋找相關網頁檔。

xargs

xargs使用起來就沒有像find -exec可以將檔名以{}的形式傳給指令去安排。xargs只能收管線資料當成指令參數來執行。

雖然使用上沒有靈活,但管線指令就是他的優點,所以他不只可以搭配find指令,echo資料給他也可以,譬如

echo a b c d e | xargs touch

一次創造了a b c d e五個檔案。如果find後面不用-exec,可以改用管線指令傳給xargs,譬如本篇第一個刪除檔案的例子,改用xargs變成

find -name "*.log" | xargs rm

但是後面改檔名的例子,就沒辦法這麼簡單的處理(可以處理,但改用xargs還要搭配其他指令,本篇已經太長,不再節外生枝介紹)

另外像改檔案內容的指令,因為檔名參數接在最後面,所以也可以很簡單的改用xargs來處理

find . -type f | xargs sed -i 's/www.old.com/www.new.com/g'

因此,當我用find在處理檔案時,我會用find -exec比較靈活。其他情況下我就用管線串流到xargs來處理

2014年12月9日 星期二

HTML, CSS, 相對視窗或螢幕的高度與寬度

w3school.com網站,CSS Units有各種與寬度的表示法

以我使用的頻率來排序:

  • px: 使用螢幕幾個像素(但是還要考慮Retina螢幕像素是一般螢幕像素的兩倍)
  • %: 相對父層的大小比例
  • vh, vw: 相對於瀏覽器展示網頁區域的大小(不是整個瀏覽器的大小,沒包含瀏覽器的工具列,只有展示網頁的區域)
  • vmin: vh, vw取最小值(另外還有vmax則是取最大值,但是目前IE跟safari不支援)

px%很常用,vhvwvmin是CSS3的新產物,表示相對瀏覽器展示頁面的大小


相對視窗大小

這邊先說明,vhvwvmin只包含網頁顯示區域的長寬,不包含瀏覽器的工具列

先從dom的最根本講起好了,一份HTML文件,根是<html></html>(雖然沒有嚴格規定,不寫也能顯示),然後這個根的父元件就是瀏覽器的網頁頁面顯示區

因此,如果對<html></html>宣告大小是100%就跟宣告100vh一樣,因為都是指瀏覽器的網頁頁面顯示區大小

這個範例是將html設定為100vh

<html style="height: 100vh">
    <head>
        <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>

        <script type="text/javascript">
            console.log($('html').height());
        </script>
    </head>
</html>

這個範例是將html設定為100%

<html style="height: 100%">
    <head>
        <script src="https://code.jquery.com/jquery-1.11.1.min.js"></script>

        <script type="text/javascript">
            console.log($('html').height());
        </script>
    </head>
</html>

這兩者在瀏覽器的console應該輸出一樣的大小

如果從html, body到div , 這些tag都填入100%,則從頭到尾都填滿整個視窗(當然還注意body預設有padding, margin佔有一些寬度,要把他們都設為0,才會真的佔滿全畫面)

不過現在有vh, vw,就不用這麼麻煩了,可以只設定長度為100vh, 寬度為100vw,就可以填滿整個畫面(依然得注意body預設佔有padding跟margin)

像下面這樣就能把兩個div排在一起,並且以瀏覽器寬度3:7的比例,分割成兩個區塊

<html>
    <head></head>
    <body style="margin: 0;padding: 0;">
        <div style="width: 30vw;float:left;">Left...</div>
        <div style="width: 70vw;float:left;">Right...</div>
    </body>
</html>

還有一個問題,以往使用桌上型電腦的螢幕,我們可以預期大部分人的電腦,都是寬度大於長度,大部份的人也都是用全螢幕瀏覽網頁。所以我們很信任的將長寬都設定成vh就可以變成正方形,並且不超過瀏覽器大小。

但是現在手機螢幕,可以拿直的,也可以拿橫的,那要怎麼辦呢?就使用vmin

譬如我們要顯示一張正方形的圖片,希望以最大張的方式呈現

<html>
    <head></head>
    <body style="margin: 0;padding: 0;">
        <img src="圖片路徑" style="width: 100vmin;height: 100vmin;">
    </body>
</html>

這樣就不怕桌機或手機,螢幕擺直得或橫的,都以最大張,且不會超出瀏覽器範圍欄顯示圖片


相對螢幕大小

那可不可以相對於使用者整個螢幕的大小?

可以,但是要使用Javascript去設定,在Javascript裡面直接使用screen.heightscreen.width,就可以取得螢幕大小(關於screen是什麼?請參考w3school的相關頁面)

之後再用dom語法,去改你要改的地方,假設你要將所有的div tag寬度都隨使用者螢幕寬度改變,這邊使用jQuery:

$('div').width(screen.width);

這時候不管使用者怎麼拉動瀏覽器的寬度,所有的div tag寬度都等是螢幕寬度

如果想單純使用javascipt的話,語法比較冗長一點,可以參考w3chool的頁面,注意他傳入的參數後面還要接單位,所以在screen.width之後記得再加上"px",之後才當引數傳進去

回到原本的jQuery版本,如果想要75%的螢幕大小

$('div').width(screen.width * 0.75);

想要30%的螢幕大小

$('div').width(screen.width * 0.3);

以此類推,我一開始會害怕對screen.width做數學運算,因為不確定內容是字串還是數字。但是多慮了,javascript可以對數字做數學運算,也可以對內容是數字的字串做數學運算

'10' * '10' // 輸出100
'10' * 10 // 也輸出100

如果是在Ruby,則是輸出"10101010101010101010",所以在javascript裡面,不用怕,他內容是數字,就直接對他做數學運算,不用先檢查型別。(若字串內容不是數字,會輸出NaN,Not a Number的意思)

2014年12月4日 星期四

Linux, bash, 壓縮, tar, bz2, xz, 7z, 壓縮率

Linux, bash, 壓縮, tar, bz2, xz, 7z, 壓縮率

如果只是想查各種副檔名的解壓縮指令,只要Google副檔名就可以找到(例如:tar.bz2)

這邊主要是在對資料夾壓縮時,為了自訂壓縮率而深入研究壓縮指令而產生。

tar 可以對檔案或資料夾進行單存的打包(或解開打包),後面接c代表打包,x代表解開打包。這邊在Bash底下產生一個簡單的文字檔來練習:

echo "Hello World" > text
tar c text
# 輸出 text000644 000765 000000 00000000014 12437746653 012241 0ustar00wemeewheel000000 000000 Hello World

# 如果要儲存,加上參數f,並指定存檔名稱
tar cf text1.tar text

# 或者使用串流來寫入檔案也可以(實際應用不建議)
tar c text > text2.tar

原始檔案12B, 打包後2K, 使用串流寫入的10K, 可以確定打包完全不會有壓縮的效果

 12B 12  4 10:57 text
2.0K 12  4 11:01 text1.tar
 10K 12  4 11:01 text2.tar

其實打包主要目的在於,我常用的bz2壓縮,只能對檔案壓縮,不能對目錄壓縮,所以才需要先把目錄打包成一個檔案,再用bz2去壓縮它

接下來,弄個目錄跟文字檔來測試

mkdir dir
echo "Hello World" > dir/text

tar cf dir1.tar dir
tar c dir > dir2.tar

結果一樣,使用串流寫入的,佔的容量比較大

102B 12  4 11:28 dir
2.5K 12  4 11:44 dir1.tar
 10K 12  4 11:44 dir2.tar

接下來,使用bz2壓縮看看

bzip2 -z dir1.tar
bzip2 -z dir2.tar

來看看所佔用的容量

102B 12  4 11:28 dir
167B 12  4 11:44 dir1.tar.bz2
169B 12  4 11:44 dir2.tar.bz2

壓縮過後都變小了,分成先打包,再用bz2壓縮,其實是因為tar指令只是打包,頂多只能加上參數j,表示打包過程使用bz2,(或參數z,表示打包過程使用gz),無法指定壓縮比

# 將目錄打包之後,再用bz2壓縮並指定壓縮比
bzip2 -9 -z dir1.tar

如果要寫成一行的話,可以打包時,輸出到串流,在使用管線指令輸入到bzip2, 因為輸入是串流,bzip2無法指定要壓縮的檔案,所以把壓縮結果使用參數c,再一次輸出到串流,並儲存成檔案

tar c dir | bzip2 -c > dir.tar.bz2

# tar 無法指定壓縮比,在bzip2指定壓縮比
tar c dir | bzip2 -9 -c > dir.tar.bz2

同理,如果使用xz壓縮時,想一併指定壓縮比,也用同樣方式

tar c dir | xz -9 -c > dir.tar.xz

因為bz2壓縮,在Windows開啟中文檔名,常常變成亂碼,可以改用7zip來壓縮,如果使用7zip,他沒有辦法吃管線進來的資料,那怎麼辦?分兩行指令,先打包在壓縮嗎?

不用啦~ 7zip可以直接對目錄壓縮,直接壓縮,只是它指定壓縮比的參數是-mx數字

7za a -mx9 dir.7z dir

這樣子就直接對dir這個目錄以最大壓縮的壓縮比去執行了

2014年12月3日 星期三

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby, dup跟clone的不同, 淺層複製與深層複製

Ruby裡面,對於物件的複製有兩個方法,分別是對物件呼叫dupclone,這兩種有什麼不同呢?這兩種方法並非只是alias的名稱而已。

先不討論淺層複製深層複製的問題,先來看看呼叫dup做了什麼事情。假設有個class:

class Foo
    def foo
        'foo'
    end
end

我們來試試看,產生Foo物件,然後對他呼叫dup

foo = Foo.new
dup_foo = foo.dup

dup_foo.foo # => 'foo'

一切依照預期,順利的進行複製了,接下來我們對foo動態增加一些Method上去,看看能不能夠複製(至於如何對物件動態增加Method請參考這篇)

class << foo
    def run
        'run'
    end
end

foo.foo # => 'foo' 原本class就有宣告 沒問題
foo.run # => 'run' 動態新增的Method 也沒問題

dup_foo_2 = foo.dup
dup_foo_2.foo # => 'foo' 原本class就有宣告 沒問題
dup_foo_2.run # => 發生錯誤,No Method Error

由此,可以知道,dup方法,僅對完整宣告的class進行複製,動態增加的不會複製,那麼我們再多測試一點,使用另一種方法增加Method。我們把整個程式碼完整再寫過

class Foo
    def foo
        'foo'
    end
end

foo = Foo.new
dup_foo = foo.dup

# 使用另一種方法增加Method到Foo上面去
class Foo
    def go
        'go'
    end
end

foo.go # => 'go'
dup_foo.go # => 'go', 正常執行

由此可見,只要是宣告成所有class Foo可以使用的Methods都可以被dup所複製,只有動態為單一Foo物件增加的Methods不能被dup複製進去

那麼clone方法,相對dup方法,就是完整地複製出一模一樣的物件

class Bar
    def bar
        'bar'
    end
end

bar = Bar.new

class << bar
    def run
        'run'
    end
end

clone_bar = bar.clone
clone_bar.bar # => 'bar'
clone_bar.run # => 'run', 動態增加的方法也一併複製了

接下來,來探討一下淺層複製深層複製的問題,都只是淺層複製

ary = ['hello']
dup_ary = ary.dup
clone_ary = ary.clone

dup_ary[0][0] = 'X'
p ary # => ["Xello"], 原本的ary被改掉了

clone_ary[0][0] = 'Y'
p ary # => ["Yello"], 原本的ary也被改掉了

如果要進行深層複製,就得遞迴對陣列中每個物件,都進行複製。

在Ruby on Rails裡面,直接提供deep_dup(但沒有提供deep_clone)

如果是在傳統的Ruby,我們可以自己實作deep_dup方法,來進行深層複製,在stackoverflow上面,我找到一個不錯的寫法

# 寫得不錯,如果物件是Array, 就繼續遞迴
class Array
    def deep_dup
        map {|x| x.deep_dup}
    end
end

# 直到不是Array,就進行複製,但這邊沒考慮到Ruby的容器還有Hash, Set ... 等,這些也需要做深層複製,所以這邊若遇到Hash, Set之類的容器就變成淺層複製了
class Object
    def deep_dup
        dup
    end
end

# 這邊小心Numeric類別的物件,沒有dup方法,所以就直接回傳本身
class Numeric
    def deep_dup
        self
    end
end

這個例子只有對Array類別進行深層複製,如果你有用到Hash, Set,甚至是你自己寫的容器,自己實做的iterator pattern ... 等,就得自行再加上deep_dup方法上去。