背景
在實(shí)際項(xiàng)目實(shí)施中,會編寫很多在服務(wù)器執(zhí)行的作業(yè)腳本瓶佳。程序中凡是涉及到數(shù)據(jù)庫鏈接、操作系統(tǒng)用戶鏈接鳞青、IP地址霸饲、主機(jī)名稱的內(nèi)容都是敏感信息。在純內(nèi)網(wǎng)系統(tǒng)中往因?yàn)殚_發(fā)時間緊迫臂拓,往往都直接將這些敏感信息明文方式寫在腳本中了厚脉。
稍微規(guī)范一點(diǎn)的,創(chuàng)建一個通用的config文件胶惰,將所有這類敏感信息記錄在這個文件中傻工,腳本以讀取文件方式獲取這些信息。這種方式的好處是腳本不用在應(yīng)用遷移孵滞、災(zāi)備部署的時候再起不同的版本中捆,尤其是大數(shù)據(jù)平臺作業(yè)運(yùn)行的腳本,如果是需要做災(zāi)備集群坊饶,這種方式可以減少生產(chǎn)變更時的人工干預(yù)操作泄伪。但是這種方式仍不能解決安全性的問題,只要config文件泄露幼东,那么平臺會非常危險臂容。
因此在這個config文件的基礎(chǔ)上,對其進(jìn)行改造根蟹,實(shí)現(xiàn)對內(nèi)容的加密脓杉,而腳本使用時再對其進(jìn)行解密。因此要求有一個程序能對文本內(nèi)容進(jìn)行加密简逮,也能進(jìn)行反向解密球散。
不可逆的加密方法使用最多的就是md5加密算法,我們一般用來檢驗(yàn)文件的完整和安全性散庶,不適用這個場景蕉堰。
使用python語言對文本內(nèi)容進(jìn)行加解密有多種方式,從網(wǎng)上搜索結(jié)果看主要有以下幾種:
1.方法一 使用base64轉(zhuǎn)編碼
Base64是一種用64個字符來表示任意二進(jìn)制數(shù)據(jù)的方法悲龟。
用記事本打開exe屋讶、jpg、pdf這些文件時须教,我們都會看到一大堆亂碼皿渗,因?yàn)槎M(jìn)制文件包含很多無法顯示和打印的字符,所以轻腺,如果要讓記事本這樣的文本處理軟件能處理二進(jìn)制數(shù)據(jù)乐疆,就需要一個二進(jìn)制到字符串的轉(zhuǎn)換方法。Base64是一種最常見的二進(jìn)制編碼方法贬养。
Base64的原理很簡單挤土,首先,準(zhǔn)備一個包含64個字符的數(shù)組:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
然后误算,對二進(jìn)制數(shù)據(jù)進(jìn)行處理仰美,每3個字節(jié)一組,一共是3x8=24bit儿礼,劃為4組筒占,每組正好6個bit,得到4個數(shù)字作為索引,然后查表蜘犁,獲得相應(yīng)的4個字符翰苫,就是編碼后的字符串。
所以这橙,Base64編碼會把3字節(jié)的二進(jìn)制數(shù)據(jù)編碼為4字節(jié)的文本數(shù)據(jù)奏窑,長度增加33%,好處是編碼后的文本數(shù)據(jù)可以在郵件正文屈扎、網(wǎng)頁等直接顯示埃唯。
如果要編碼的二進(jìn)制數(shù)據(jù)不是3的倍數(shù),最后會剩下1個或2個字節(jié)怎么辦鹰晨?
Base64用\x00字節(jié)在末尾補(bǔ)足后墨叛,再在編碼的末尾加上1個或2個=號止毕,表示補(bǔ)了多少字節(jié),解碼的時候漠趁,會自動去掉扁凛。
Python內(nèi)置的base64可以直接進(jìn)行base64的編解碼:
import base64
userPassword="sunlinemdp201810"
unkownPassword=base64.b64encode(bytes(userPassword,'utf-8'))
print("加密后:"+str(unkownPassword,'utf-8'))
kownPassword=str(base64.b64decode(unkownPassword),'utf-8')
print('解密后:'+kownPassword)
補(bǔ)充說明:python3中對字符串加解密的方法 base64.encodestring('test') 不能用 因此只能采用bytes方法然后中間進(jìn)行格式轉(zhuǎn)換
由于標(biāo)準(zhǔn)的Base64編碼后可能出現(xiàn)字符+和/,在URL中就不能直接作為參數(shù)闯传,所以又有一種"url urlsafe_b64encode"的base64編碼方法谨朝,實(shí)現(xiàn)對+和/的轉(zhuǎn)碼,在現(xiàn)在使用的版本中這個功能已經(jīng)整合在b64encode方法中了
userPassword="sunlinemdp201810"
unkownPassword=base64.b64encode(bytes(userPassword,'utf-8'))
print("加密后:"+str(unkownPassword,'utf-8'))
unkownPassword=base64.urlsafe_b64encode(bytes(userPassword,'utf-8'))
print("解密后:"+str(unkownPassword,'utf-8'))
base64.urlsafe_b64decode(unkownPassword)
2.方法二 win32com.client
樣例代碼如下:
import win32com.client
def encrypt(key,content): # key:密鑰,content:明文
EncryptedData = win32com.client.Dispatch('CAPICOM.EncryptedData')
EncryptedData.Algorithm.KeyLength = 5
EncryptedData.Algorithm.Name = 2
EncryptedData.SetSecret(key)
EncryptedData.Content = content
return EncryptedData.Encrypt()
def decrypt(key,content): # key:密鑰,content:密文
EncryptedData = win32com.client.Dispatch('CAPICOM.EncryptedData')
EncryptedData.Algorithm.KeyLength = 5
EncryptedData.Algorithm.Name = 2
EncryptedData.SetSecret(key)
EncryptedData.Decrypt(content)
str = EncryptedData.Content
return str
s1 = encrypt('sunline', 'hello world')
s2 = decrypt('sunline', s1)
print s1,s2
win32com是python操作windows程序的第三方包甥绿,放在服務(wù)器上使用不太合適字币。
3.方法三 PyCrypto
一個極好的用于信息安全的python庫,包括所有主流算法共缕。
具體可以參考:
附pycrypto調(diào)用方法
服務(wù)器文件加密實(shí)現(xiàn)
現(xiàn)在假定要對一個存儲各類ip洗出、賬戶、密碼的global.properties文件進(jìn)行加密图谷,同時共苛,支持在讀取時進(jìn)行解密。
global.properties的內(nèi)容假定如下圖所示:
bgp.inceptor.in1.ip=10.22.179.13
bgp.inceptor.in1.default=cmr
bgp.inceptor.in2.ip=10.22.179.14
bgp.inceptor.in2.default=default
bdp.ldap.mdp.username=mdp
bdp.ldap.mdp.password=mdp
每一行使用等號將信息分為兩段蜓萄,等號左邊是信息項(xiàng)名稱隅茎,等號右邊是信息項(xiàng)具體的內(nèi)容,我們要對信息項(xiàng)的具體的內(nèi)容進(jìn)行加密嫉沽。
首先做需求分析辟犀,我們的需求可以拆分為以下幾個:
- 實(shí)現(xiàn)對指定字符串內(nèi)容基于某個密鑰的加解密內(nèi)容輸出
- 讀取指定加密配置文件,根據(jù)信息項(xiàng)名稱讀取指定內(nèi)容后加解密輸出
- 讀取指定配置文件绸硕,對文件內(nèi)每一行信息項(xiàng)的具體內(nèi)容進(jìn)行加解密后生成新的加解密后的配置文件
第二步做程序設(shè)計堂竟,我是從功能上進(jìn)行拆分設(shè)計:
基本功能包括:
- 加密解密
- 獲取文件
- 文件指定內(nèi)容讀取
交互操作包括:
- 參數(shù)讀取解析
- 具體功能實(shí)現(xiàn),輸出結(jié)果或生成文件
輸入輸出設(shè)計:
- 輸入: 功能類型 -d 對應(yīng) 四種功能需求
- 輸入: 密鑰 -k 密鑰內(nèi)容
- 輸入: 加密內(nèi)容或解密內(nèi)容 -c 對應(yīng)功能2
- 輸入: 配置文件路徑名稱 -f 對應(yīng)功能 234
- 輸入: 信息項(xiàng)內(nèi)容 -i 對應(yīng)功能4
- 輸出: 加解密文 對應(yīng)功能 1 4
- 輸出: 文件路徑及名稱 對應(yīng)功能 2 3
第三步玻佩,首先根據(jù)他人提供的方法做了一個對具體字符串進(jìn)行加解密的類出嘹,唯一多做的處理就是對加密使用的密鑰多了一個base64編碼的過程,文件保存為optcrypt.py:
#coding: utf8
'''
實(shí)現(xiàn)對指定字符串內(nèi)容基于某個密鑰的加解密內(nèi)容輸出
密鑰使用base64多加一層處理
version: v0.0.1
author: Duwj
date: 2018-10-24
'''
import sys
from Crypto.Cipher import AES
from binascii import b2a_hex, a2b_hex
import base64
class optcrypt():
def __init__(self, key):
self.key = str(base64.b64decode(key))
self.mode = AES.MODE_CBC
#self.iv = Random.new().read(AES.block_size)
#加密函數(shù)咬崔,如果text不是16的倍數(shù)【加密文本text必須為16的倍數(shù)税稼!】,那就補(bǔ)足為16的倍數(shù)
def aesencrypt(self, text):
#密鑰key 長度必須為16(AES-128)垮斯、24(AES-192)郎仆、或32(AES-256)Bytes 長度.目前AES-128足夠用
cipher = AES.new(self.key, self.mode, self.key)
#cipher=AES.new(bytes(self.key), self.mode,Random.new().read(AES.block_size))
#加密文本text必須為16的倍數(shù)
add = 16 - (len(text) % 16)
text = text + ('\0' * (16 - (len(text) % 16)))
self.ciphertext = cipher.encrypt(text)
#因?yàn)锳ES加密時候得到的字符串不一定是ascii字符集的,輸出到終端或者保存時候可能存在問題
#所以這里統(tǒng)一把加密后的字符串轉(zhuǎn)化為16進(jìn)制字符串
return b2a_hex(self.ciphertext)
#解密
def aesdecrypt(self, text):
cryptor = AES.new(self.key, self.mode, self.key)
#16進(jìn)制轉(zhuǎn)回后解密
plain_text = cryptor.decrypt(a2b_hex(text))
#rstrip()去掉補(bǔ)的空格
return plain_text.rstrip('\0')
if __name__ == '__main__':
#設(shè)置環(huán)境編碼
reload(sys)
sys.setdefaultencoding('utf8')
#測試
pc = optcrypt('c3VubGluZW1kcDIwMTgxMQ==')
e = pc.aesencrypt("duwj")
d = pc.aesdecrypt("d518cdd30b854b84f5aa7c5511e03e38")
print "info:duwj,encrypt:"+e+",decrypt:"+d
然后就是依托這個基礎(chǔ)的字符串加解密類兜蠕,實(shí)現(xiàn)對字符串扰肌、文件、信息項(xiàng)的加解密功能熊杨,在這個過程中沒有對復(fù)雜的properties結(jié)構(gòu)進(jìn)行解析曙旭,單純的使用=
實(shí)現(xiàn)信息項(xiàng)和密文內(nèi)容的分離盗舰,文件保存為server.py:
#!bin/python
#-*- coding: UTF-8 -*-
'''
腳本功能:
1. 實(shí)現(xiàn)對指定字符串內(nèi)容基于某個密鑰的加解密內(nèi)容輸出
2. 讀取指定加密配置文件,根據(jù)信息項(xiàng)名稱讀取指定內(nèi)容后加解密輸出
3. 讀取指定配置文件桂躏,對文件內(nèi)每一行信息項(xiàng)的具體內(nèi)容進(jìn)行加解密后生成新的加解密后的配置文件
使用說明:python server.py
-d [選擇方法[se 字符串加密 /sd 字符串解密/ie 信息項(xiàng)加密/id 信息項(xiàng)解密/fe 文件加密/fd 文件加密]]
-k [16位密鑰]
-c [加密內(nèi)容]
-f [文件名稱]
-i [信息項(xiàng)名稱]
字符串加解密必選項(xiàng): -k -c
信息項(xiàng)加解密必選項(xiàng): -k -f -i
文件加解密必選項(xiàng): -k -f
version: v0.0.1
author: Duwj
date: 2018-11-05
'''
import os
import sys
import getopt
from optcrypt import optcrypt
#字符串加解密
def stringCrpyt(func_name,value):
if func_name =="se":
crpyt_value=pc.aesencrypt(value)
else:
crpyt_value=pc.aesdecrypt(value)
return crpyt_value
#信息項(xiàng)加解密輸出
def infoCrpyt(func_name,file_name,info_name):
value=""
try:
pro_file = open(file_name, 'Ur')
for line in pro_file.readlines():
line = line.strip().replace('\n', '')
if info_name == line.split('=')[0]:
value = line.split('=')[1]
#print value
except Exception, e:
raise e
finally:
pro_file.close()
if func_name == "ie":
crpyt_value=pc.aesencrypt(value)
elif func_name=="id":
crpyt_value=pc.aesdecrypt(value)
return crpyt_value
#文件加解密
def fileCrpyt(func_name,file_name):
try:
read_file = open(file_name,'Ur')
write_file = open(file_name+"."+func_name+"crypt",'w')
if func_name == "fe":
for line in read_file.readlines():
line = line.strip().replace('\n', '')
if line=="":
pass
elif line.find("#")!=-1:
write_file.write(line+"\n")
else:
strs=line.split('=')[0]+"="+pc.aesencrypt(line.split('=')[1])
write_file.write(strs+"\n")
else:
for line in read_file.readlines():
line = line.strip().replace('\n', '')
if line.find("#")!=-1:
write_file.write(line+"\n")
else:
strs=line.split('=')[0]+"="+pc.aesdecrypt(line.split('=')[1])
write_file.write(strs+"\n")
except Exception, e:
raise e
finally:
read_file.close()
write_file.close()
return file_name+"."+func_name+"crypt"
if __name__ == "__main__":
#設(shè)置環(huán)境編碼
reload(sys)
sys.setdefaultencoding('utf8')
msg='''使用說明:python server.py
-d [選擇方法[se 字符串加密 /sd 字符串解密/ie 信息項(xiàng)加密/id 信息項(xiàng)解密/fe 文件加密/fd 文件加密]]
-k [16位密鑰]
-c [加密內(nèi)容]
-f [文件名稱]
-i [信息項(xiàng)名稱]
字符串加解密必選項(xiàng): -k -c
信息項(xiàng)加解密必選項(xiàng): -k -f -i
文件加解密必選項(xiàng): -k -f
'''
#獲取參數(shù)
opts, args = getopt.getopt(sys.argv[1:], "d:k:c:f:i:")
if len(opts)==0:
print msg
sys.exit(0)
for op, value in opts:
if op == "-d":
func_name = value
elif op == "-k":
key_content = value
elif op == "-c":
txt_content = value
elif op == "-f":
file_name = value
elif op == "-i":
info_name = value
#print(opts)
#初始化密鑰
pc = optcrypt(key_content)
#根據(jù)功能類型執(zhí)行
if func_name in("se","sd"):
print stringCrpyt(func_name,txt_content)
elif func_name in ("ie","id"):
print infoCrpyt(func_name,file_name,info_name)
elif func_name in ("fe","fd"):
print fileCrpyt(func_name,file_name)
else:
print("there is no function named "+func_name)
sys.exit(0)
注意在項(xiàng)目文件夾中增加一個init.py文件以便于腳本能識別optcrypt模塊钻趋。
測試腳本test.sh:
#!/bin/bash
#依賴python pycrypt模塊 安裝這個模塊的命令是 python setup.py install
#系統(tǒng)必須現(xiàn)安裝yum install python-devel
#測試加解密程序
#c3VubGluZW1kcDIwMTgxMQ== 是對 sunlinemdp201811 進(jìn)行base64編碼后的值 可以修改 方法為:
# 1.命令行執(zhí)行 python 進(jìn)入python編程環(huán)境
# 2.執(zhí)行以下代碼
# import base64
# print s= base64.b64encode("sunlinemdp201811") # 引號內(nèi)為想編碼的密鑰文本
#幫助
python server.py
#字符串加密
python server.py -d se -k c3VubGluZW1kcDIwMTgxMQ== -c sunline
#字符串解密
python server.py -d sd -k c3VubGluZW1kcDIwMTgxMQ== -c cd3f4a3b1c4a189d3fe985495f6f963b
#文件加密
python server.py -d fe -k c3VubGluZW1kcDIwMTgxMQ== -f global.properties
#文件解密
python server.py -d fd -k c3VubGluZW1kcDIwMTgxMQ== -f global.properties.fecrypt
#信息項(xiàng)加密輸出
python server.py -d ie -k c3VubGluZW1kcDIwMTgxMQ== -f global.properties -i database.ora10g.username
#信息項(xiàng)解密輸出
python server.py -d id -k c3VubGluZW1kcDIwMTgxMQ== -f global.properties.fecrypt -i database.ora10g.username
#shell中獲取python輸出值的方法:
outputString=`python server.py -d ie -k c3VubGluZW1kcDIwMTgxMQ== -f global.properties -i database.ora10g.username`
echo outputString:${outputString}
outputFile=`python server.py -d fe -k c3VubGluZW1kcDIwMTgxMQ== -f global.properties`
echo outputFile:${outputFile}
輸出結(jié)果:
[root@localhost encrypt]# ./test.sh
使用說明:python server.py
-d [選擇方法[se 字符串加密 /sd 字符串解密/ie 信息項(xiàng)加密/id 信息項(xiàng)解密/fe 文件加密/fd 文件加密]]
-k [16位密鑰]
-c [加密內(nèi)容]
-f [文件名稱]
-i [信息項(xiàng)名稱]
字符串加解密必選項(xiàng): -k -c
信息項(xiàng)加解密必選項(xiàng): -k -f -i
文件加解密必選項(xiàng): -k -f
cd3f4a3b1c4a189d3fe985495f6f963b
sunline
global.properties.fecrypt
global.properties.fecrypt.fdcrypt
d518cdd30b854b84f5aa7c5511e03e38
dw
outputString:d518cdd30b854b84f5aa7c5511e03e38
outputFile:global.properties.fecrypt
后續(xù)會對腳本進(jìn)行內(nèi)容補(bǔ)充,主要是增加一些之前為了功能實(shí)現(xiàn)而忽略的異常處理和日志登記的內(nèi)容沼头。