根据Request获取客户端IP

在java里,获取客户端的IP地址的方法是:request.getRemoteAddr() ,这种方法在大部分情况下都是有效的。但是在通过了Apache,Squid等反向代理软件就不能获取到客户端的真实IP地址了。
如果使用了反向代理软件,将http://192.168.1.110:2046/ 的URL反向代理为http://www.xxx.com/ 的URL时,用request.getRemoteAddr() 方法获取的IP地址是:127.0.0.1 或 192.168.1.110 ,而并不是客户端的真实IP。
经过代理以后,由于在客户端和服务之间增加了中间层,因此服务器无法直接拿到客户端的IP,服务器端应用也无法直接通过转发请求的地址返回给客户端。但是在转发请求的HTTP头信息中,增加了X-FORWARDED-FOR信息。用以跟踪原有的客户端IP地址和原来客户端请求的服务器地址。当我们访问http://www.xxx.com/index.jsp/ 时,其实并不是我们浏览器真正访问到了服务器上的index.jsp文件,而是先由代理服务器去访问http://192.168.1.110:2046/index.jsp ,代理服务器再将访问到的结果返回给我们的浏览器,因为是代理服务器去访问index.jsp的,所以index.jsp中通过request.getRemoteAddr() 的方法获取的IP实际上是代理服务器的地址,并不是客户端的IP地址。
于是可得出获得客户端真实IP地址的方法一:

[java]
public String getRemortIP(HttpServletRequest request) {
if (request.getHeader("x-forwarded-for") == null) {
return request.getRemoteAddr();
}
return request.getHeader("x-forwarded-for");
}
[/java]

可是当我访问http://www.xxx.com/index.jsp/ 时,返回的IP地址始终是unknown,也并不是如上所示的127.0.0.1 或 192.168.1.110 了,而我访问http://192.168.1.110:2046/index.jsp 时,则能返回客户端的真实IP地址,写了个方法去验证。原因出在了Squid上。squid.conf 的配制文件 forwarded_for 项默认是为on,如果 forwarded_for 设成了 off  则:X-Forwarded-For: unknown
于是可得出获得客户端真实IP地址的方法二:

[java]
public String getRemoteHost(javax.servlet.http.HttpServletRequest request){
String ip = request.getHeader("x-forwarded-for");
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getHeader("WL-Proxy-Client-IP");
}
if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)){
ip = request.getRemoteAddr();
}
return ip.equals("0:0:0:0:0:0:0:1")?"127.0.0.1":ip;
}
[/java]

Python3实现基于模板及逆向数据库的代码生成器

这段时间由于有大量重复的代码工作,因此打算用代码生成器来提高工作效率。原理十分简单,根据数据库表名逆向工程得到表信息,利用得到的信息生成代码文件。
需要提的是一开始我才用的是字符串拼接和参数的方式来完成的,但是第一效率太低,而且几乎没有通用性,每个类型的文件都得写大量代码来进行字符串操作。后来改成了利用正则表达式对template文件进行操作,这样就可以把输出内容与代码隔离。当需要导入新模板时,只需要将一篇现成的代码保存为template文件然后用标签将其中的关键词替换就可以了。

主要代码分为两大块,一是核心的parser,利用正则表达式对标签进行解析;二是参数生成部分,主要是接受用户输入,然后逆向数据库得到字段信息,从用户输入和字段信息中提取关键信息,最后根据配置文件调用相应的模板文件和参数一起送入parser完成解析。因此如果想要增加参数,只需要在这部分添加代码即可。

目前为止,生成器提供一下几种标签:
1.{name},简单替换。替换为name所对应的值。
2.{{?name:content?}},可选内容。如果参数中的chosen数组不包含name则这段会被删除,否则将修饰用的标签删除保留content部分。
3.{{!name:content!}},循环。这里的name应该是参数中所提供的dict list,parser将遍历lsit,同时用当前的dict中的参数对content进行置换,遍历完成后将所有content拼接在一起后置入文档中。另外,content内目前实现了`{!a|b!}的语法,当不是最后一次循环时会调用啊,否则调用b,后期打算更改为根据输入条件来选择输出内容。

具体代码就暂时不放了,现在的虽然能用,但是功能不够完善,2和3都需要改进,而且还有文档引用的功能没加,等全部完善了在放代码吧。

EasyUI-window组件中包含iframe可能引发重复请求和jquery异常

准备工作:在A页面中引入jquery,下面放一个easyui的window,在window中包含一个iframe,src指向B页面。B页面只需要引入jquery即可。
[html]
<div id="win" class="easyui-window">
<iframe src=’……’></iframe>
</div>
[/html]

我分别用Firefox Dev Edition 36.0a2,chrome 39.0.2171.95 m,IE 11.0.15进行了测试。其中ff和chrome正常,ie在jquery初始化过程中发生异常,无法完成初始化,子页面中无法调用jquery。
对dom树进行分析发现包含iframe标签的div已经被移动到新的位置上,因此怀疑easyui在初始化window的过程中对dom树的操作导致iframe初始化异常。于是查看网络请求,发现在ff中对B页面发起了两次请求,并且全部完成。chrome中对B页面发起了三次请求但其中两次被取消,ie中对B页面发起三次请求并且全部成功。可见easyui控件在初始化的过程中是会导致内部的iframe重复加载。
解决方案是将iframe中的src改为data-src,然后在easyui初始化完成后调用$(‘#frame’).attr(‘src’, $(‘#frame’).data(‘src’))来实现延迟加载即可避免子页面出错。

PythonChallenge解题记录(0-17)

群里的每日一题计划似乎进行了没几次就被群主遗忘了,我在闲极无聊之下找到了PythonChallenge。pc的玩法基本上就是根据每一关的提示找出下一关的密码,和闯关游戏类似。不过pc的特点是解析下一关地址的过程极其复杂,无法人工完成,只能借助计算机编程实现。pc主要涉及了网络爬虫,图像处理和文件操作,虽说没有涉及python高级技巧,但是对于python的常用功能也算是基本都覆盖到了,因此非常适合新手在缺少练手机会是拿来熟练python。最后,我的解题过程基本上市先看网上的攻略找到题目中谜题的含义,然后自己编写代码来解题,我建议大家尽量也采用这种方法,可以减少在谜面上浪费的脑细胞。

0
http://www.pythonchallenge.com/pc/def/0.html
根据提示2^38得到274877906944
[python]
2 ** 38
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/274877906944.html

1
http://www.pythonchallenge.com/pc/def/map.html
页面包含一段乱码:g fmnc wms bgblr rpylqjyrc gr zw fylb. rfyrq ufyr amknsrcpq ypc dmp. bmgle gr gl zw fylb gq glcddgagclr ylb rfyr’q ufw rfgq rcvr gq qm jmle. sqgle qrpgle.kyicrpylq() gq pcamkkclbcb. lmu ynnjw ml rfc spj.
文字提示:everybody thinks twice before solving this.
图片提示:k->m,o->q,e->g
根据图片提示得出是凯撒密码,将字母后移两位得到”i hope you didnt translate it by hand. thats what computers are for. doing it in by hand is inefficient and that’s why this text is so long. using string. maketrans() is recommended. now apply on the url.”
用同样的方式处理url”map”得到”ocr”
此处使用正则表达式匹配所有字母以避免影响到其他字符,匹配成功后使用lambda表达式完成字符替换
[python]
re.compile(‘[a-z]’).sub(lambda m: chr((ord(m.group(0)) - 97 + 2) % 26 + 97), hint)
re.compile(‘[a-z]’).sub(lambda m: chr((ord(m.group(0)) - 97 + 2) % 26 + 97), ‘map’)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/ocr.html

2
http://www.pythonchallenge.com/pc/def/ocr.html
根据提示在源代码中找到需要统计的字符串,统计完成后获得出现最少的次数min_times,筛选出所有出现次数为mint_times的字符拼接后得到equality
[python]
for i in level2_str:
if None == count.get(i):
count[i] = 1
char.append(i)
else:
count[i] += 1
min_times = min(count.items(), key=lambda x: x[1])[1]
char = filter(lambda x: min_times == count[x], char)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/equality.html

3
http://www.pythonchallenge.com/pc/def/equality.html
老样子在源代码中找到字符串,然后正则匹配出左右刚好各有三个大写字母的小写字母,最后拼接得到linkedlist
[python]
re.compile(‘[a-z][A-Z]{3}([a-z])[A-Z]{3}[a-z]’).findall(level3_str)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/linkedlist.html

4
http://www.pythonchallenge.com/pc/def/linkedlist.php
根据页面提示打开下一个页面,只不过这关页面太多,必须要借助爬虫才行。
需要注意的是这关有两处陷阱,第一处提示”Yes. Divide by two and keep going.”,需要将之前的数字直接除二得到下一个页面。第二处在中间有一个假数字,会导向一个错误页面。
[python]
pattern = ‘(?<=[^0-9])\d+$’ pattern = ‘(?<=[^0-9])\d+$’
while 1:
page = request.urlopen(base + nothing).read().decode()
match = re.search(pattern, page)
if not match:
if ‘Yes. Divide by two and keep going.’ == page:
nothing = str(int(nothing) / 2)
else:
break
else:
nothing = match.group(0)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/peak.html

5
http://www.pythonchallenge.com/pc/def/peak.html
这一关涉及到利用pickle模块进行反序列化,爬取banner.p,然后反序列化可以得到类似[[(‘ ‘, 95)], [(‘ ‘, 14), (‘#’, 5), (‘ ‘, 70), (‘#’, 5), (‘ ‘, 1)]……]的数据,列表解析后输出可以得到用井号和空格拼成的channel单词
[python]
banner = pickle.load(request.urlopen(‘http://www.pythonchallenge.com/pc/def/banner.p‘))
return ‘’.join(‘’.join([pair[0] * pair[1] for pair in line]) + ‘\n’ for line in banner)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/channel.html

6
http://www.pythonchallenge.com/pc/def/channel.html
这关和第4关类似,也是循环读取,只不过这回变成了读取压缩文件。按第四关的方法读到最后提示需要压缩文件的注释,于是把所有注释拼接起来就可以得到类似上一关的字符画,内容为hockey。进入hockey.html后提示”it’s in the air. look at the letters. “,观察组成字符画所用的字母分别是o,x,y,g,e,n。于是输入oxygen.html,成功到达下一关。
[python]

只展示与第4关不同的部分

channel = zipfile.ZipFile(‘channel.zip’)
comments.append(channel.getinfo(nothing+’.txt’).comment.decode())
channel.read(nothing+’.txt’).decode()
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/oxygen.html

7
这一关用到了图像处理,需要借助pillow库完成。
图片中央有一条灰阶,读取灰阶中每一格的RGB任一通道值,得到数字,转化为字符串后可以得到下一关的提示。再用正则将提示中的数字取出后再次转化就可以得到下一关的地址integrity。
http://www.pythonchallenge.com/pc/def/oxygen.html
[python]
img = Image.open(‘oxygen.png’)
hint = ‘’.join([chr(img.getpixel((i, 50))[0]) for i in range(0, 609, 7)])
return ‘’.join([chr(int(i)) for i in re.findall(‘\d+’, hint)])
[/python]
下一关地址:http://www.pythonchallenge.com/pc/def/integrity.html

8
这一关是用bz2解压缩,在源代码中有两个字符串,分别解压后得到’huge’和’file’,点击蜜蜂提示输入账号密码,将量单词分别输入跳转至下一个页面。
http://www.pythonchallenge.com/pc/def/integrity.html
[python]
bz2.decompress(un).decode()
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/good.html

9
这题搞得跟脑筋急转弯似的,但仍然是图像处理。源代码中有两个数组first和second,分别将其中的数字两两一组做为坐标在图上画点。完成后可以得到两幅画,分别是牛的侧身和牛头,所以答案是bull。
http://www.pythonchallenge.com/pc/return/good.html
[python]
im = Image.new("RGB", (500, 500))
for i in range(0, len(level9_first), 2):
im.putpixel((level9_first[i], level9_first[i + 1]), (255, 255, 255))
im.save(‘level9_1.png’)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/bull.html

10
找规律的脑筋急转弯:
1 = 1个1 = 11
11 = 2个1 = 21
21 = 1个2加1个1 = 1211
依次类推。
我采用正则表达式的方式对数字进行分组,如将111221拆分为[111,22,1]。然后用列表解析的方式生成下一个数,[111,22,1]将被解析为[31,22,11],最后用join合并后进行下一轮处理。最后得到第31个数长度为5808
http://www.pythonchallenge.com/pc/return/bull.html
[python]
num = ‘1’
for i in range(0, 30):
num = ‘’.join([str(len(x[1]) + 1) + x[0] for x in re.findall(r’(\d)(\1*)’, num)])
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/5808.html

11
还是图像处理,这次是将原图中所有xy坐标都为奇数和都为偶数的点取出生成单独的图片,生成完后可以明显的看到右上角有evil字样。
http://www.pythonchallenge.com/pc/return/5808.html
[python]
im = Image.open(‘cave.jpg’)
x, y = im.size
im_new = Image.new(im.mode, (int(x / 2), int(y / 2)))
for i in range(0, x, 2):
for j in range(0, y, 2):
im_new.putpixel((int(i / 2), int(j / 2)), im.getpixel((i, j)))
im_new.save(‘level11_1.jpg’)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/evil.html

12
又是一道脑筋急转弯的题。第一页中图名字是evil1.jpg,修改为evil2.jpg后看到提示将后缀名修改为gfx。修改完后成功下载evil2.gfx文件。另外根据evil1中分牌的提示可知需要将文件按分牌方式分成五份。我使用的方式是先将文件全部读出然后列表解析生成5个子bytes,最后写入新文件。写入完成后看图可知下一关地址为disproportional
lenge.com/pc/return/evil.html
[python]
source = bytearray(file.read())
s1 = bytes([source[i] for i in range(0, len(source), 5)])
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/disproportional.html

13
页面提示”phone that evil”于是在图片中的电话上按键,按到e所在的5键时跳转到了phonebook.php,内容是一个报错的xml。查了一下这里是用到了xml远程调用,需要导入xmlrpc,用xmlrpc连接上phonebook.php后调用listMethods发现有一个phone方法。于是调用phone(‘evil’),提示”He is not the evil”。再上网查了一下发现上一关中evil4.jpg有提示”Bert is evil”,于是phone(‘Bert’),返回555-ITALY,下一关为italy
http://www.pythonchallenge.com/pc/return/disproportional.html
[python]
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/italy.html

14
根据图片和源代码中的”100100 = (100+99+99+98) + (…”提示,可知需要将下方100001的图从外向里盘绕形成一个100100的新图片。按要求生成图片后结果是一只猫,进入cat.html提示”and its name is uzi. you’ll hear from him later.”。下一关地址为uzi
http://www.pythonchallenge.com/pc/return/italy.html
[python]
im = Image.open(‘pc/wire.png’)
im_new = Image.new(im.mode, (100, 100))
count = 0
for i in range(0, 50):
jump = 99 - 2
i
for j in range(i, 99 - i):
im_new.putpixel((j, i), im.getpixel((count, 0)))
im_new.putpixel((99 - i, j), im.getpixel((count + jump, 0)))
im_new.putpixel((99 - j, 99 - i), im.getpixel((count + 2 jump, 0)))
im_new.putpixel((i, 99 - j), im.getpixel((count + 3
jump, 0)))
count += 1
count += jump * 3
im_new.save("pc/level14.jpg")
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/uzi.html

15
日历显示为1xx6年1月26日周一,且右下角显示二月有29天为闰年,找出所有符合该条件的日期,分别为1176,1356,1576,1756,1976年的1月26日。然后根据源文件中的提示”he ain’t the youngest, he is the second. buy flowers for tomorrow”。5个年份中第二靠后的是1756,搜索1756年1月27日为莫扎特的生日,符合所有提示。所以,下一关地址为mozart
http://www.pythonchallenge.com/pc/return/uzi.html
[python]
for year in range(1016, 2000, 20):
t = date(year, 1, 26)
if (t+timedelta(34)).month == 2 and t.weekday() == 0:
print(t.isoformat())
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/mozart.html

16
依旧是图像处理,干脆改名叫image challenge得了……这题是根据图片中的红色线段对齐每一行像素,对齐完后得到的图中有romance字样。
http://www.pythonchallenge.com/pc/return/mozart.html
[python]
for i in range(0, 480):
flag = 0
for j in range(0, 640):
if 195 == im.getpixel((j, i)):
flag += 1
if 5 == flag:
flag = j
break
for j in range(0, 640):
im_new.putpixel(((j - flag) % 640, i), im.getpixel((j, i)))
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/romance.html

17
这一关算是一个总结,涉及了前面关卡中出图像处理外的大部分内容,同时加入了cookie的处理,而且猜谜性质更加突出了。为了完成这一关在网上差了不少的资料,总算是勉强完成了。
这一关的主图是一堆饼干,暗示的是cookie,而左下角的小图是第四关的图片,于是进入第四关,发现第四关的cookie中有提示:”info=you+should+have+followed+busynothing…”。尝试将用busynothing替换nothing,结果可行。接下来按照第四关的方式迭代,不过迭代的过程中需要收集cookie信息,每个网页都带有一字节的信息,将这些信息提取出来并且拼接得到一个bytes类型,开头是b’BZh91AY&SY’,一看就是第8关的内容bz2,用bz2解压后得到’is it the 26th already? call his father and inform him that “the flowers are on their way”. he’ll understand.’。顺便一说,在’linkedlist.php?busynothing=19242’中cookie为’+’,但实际上应该是空格,我不清楚到底是官方出错还是有什么其他的我没发现的暗示,但是我在这上面几乎浪费了一上午的时间,最后选择了当busynothing为19242时手动替换结果,才算是成功解压出信息。
言归正传,上面的提示可以看出跟之前的莫扎特有关,上网查莫扎特的父亲名字是’Leopold Mozart’。接下来用第13关的方法phone(‘Leopold’),结果是’no! i mean yes! but ../stuff/violin.php.’,进入该页面后有一张莫扎特的照片,页面标题是’it’s me. what do you want?’,总算是找到莫扎特了……接下来通过cookie给莫扎特鲜花,也就是请求时在cookie中带上’the flowers are on their way’的信息,得到结果’oh well, don\’t you dare to forget the balloons.’其中balloons就是下一关的地址
http://www.pythonchallenge.com/pc/return/romance.html
[python]
page = ‘’
base = ‘http://www.pythonchallenge.com/pc/def/linkedlist.php?busynothing=
nothing = ‘12345’
pattern = ‘(?<=[^0-9])\d+$’
cookies = []
while 1:
while 1:
try:
page = request.urlopen(base + nothing, timeout=1)
break
except:
pass
data = page.read().decode()
match = re.search(pattern, data)
cookie = page.info().get("Set-Cookie")
cookies.append(parse.unquote_to_bytes(re.search("(?<==)[^;]+(?=;)", cookie).group(0)))
if ‘19242’ == nothing:
cookies.pop()
cookies.append(b’ ‘)
if not match:
if ‘Yes. Divide by two and keep going.’ == page:
nothing = str(int(nothing) / 2)
else:
break
else:
nothing = match.group(0)

data = bz2.decompress(b’’.join(cookies))
server = client.Server("http://www.pythonchallenge.com/pc/phonebook.php&quot;)
server.phone(‘Leopold’)
http = urllib3.PoolManager().request(‘GET’, ‘http://www.pythonchallenge.com/pc/stuff/violin.php‘,
headers={‘Cookie’: "info=the flowers are on their way"})
print(http.data)
[/python]
下一关地址:http://www.pythonchallenge.com/pc/return/balloons.html

PythonChallenge做到这里也算是告一段落了,后面还有十多关没做,不过接下来这段时间会很忙,所以只能找机会慢慢完成了。

Python每日一题 第4期

题目:随机生成指定数量的随机字符串,并统计每个字符出现的次数。

随机生成并统计字母数量最后拼接为字符串,原理很简单。此处为了提高效率采取一次生成多位的方式以减少随机次数,提高效率。需要注意的是15,16两行采用位运算以提高效率,如果改为求余和求模则效率会有答复下降。
另外,这里可以采用多线程机制以进一步提高速度。
[python]
import random

seed = ‘1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}’
length = int(input(‘length:’))
count = [0] 64
out_list = []
while length > 0:
if length > 10000:
c_length = 10000
else:
c_length = length
length -= 10000
ran_int = random.randint(1, 64 *
c_length)
for i in range(0, c_length):
t = ran_int & 63
ran_int >>= 6
count[t] += 1
out_list.append(seed[t])
print(count)
print(‘’.join(out_list))
[/python]

Python每日一题 第3期

题目:判断今天是2014年的第多少天?

time.mktime(tuple) 可以将一个时间对象转化为浮点型表示的时间戳,需要传入一个时间对象「实质上是一个长度为9的元组」,其返回值为传入从1970年1月1日8时整到现在过去的秒数。
另外,struct_time对象共9个元素元素,分别为,年,月,日,时,分,秒,星期几,今年的第几天,是否夏令时。因此也可以这样调用mktime:
[python]
time.mktime((2014, 12, 11 ,21 ,50 ,30, 0, 0, 0))
[/python]

下面第一种是我一开始的写法,其基本思路是用当前时间的时间戳减去2014年1月1日0点整的时间戳得到今年过去的秒数。再除以86400后取整加1就可以得到当前是第几天。
后两种写法是在研究struct_time对象时发现的,可以直接获取struct_time格式的gmt时间和本地时间,其中第8项就是当前是本年的第几天。
[python]
import time
print(int((time.time() - time.mktime(time.strptime(‘2014’, ‘%Y’))) / (60 60 24)) + 1)
print(time.localtime()[7])
print(time.gmtime()[7])
[/python]

有关python中time和datatime的更多用法可参见help(time)及help(datatime)

Python每日一题 第2期

【我们一起学Python吧】每日一题 第2期
题目:企业发放的奖金根据利润提成。利润(I)低于或等于10万元时,奖金可提10%;利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可可提成7.5%;20万到40万之间时,高于20万元的部分,可提成5%;40万到60万之间时高于40万元的部分,可提成3%;60万到100万之间时,高于60万元的部分,可提成1.5%,高于100万元时,超过100万元的部分按1%提成,从键盘输入当月利润I,求应发放奖金总数?

[python]
ratios = ((0, 0.1), (10, 0.075), (20, 0.05), (40, 0.03), (60, 0.015), (100, 0.01))
while 1:
i_str = input(‘input a number:’)
if ‘’ == i_str:
exit(0)
i = int(i_str)
bonus = 0
last = 0
for ratio in ratios:
if ratio[0] < i:
bonus += (i - ratio[0]) * (ratio[1] - last)
last = ratio[1]
print(round(bonus, 2))
[/python]

群里的解法,利用字典实现了switch,虽然有滥用eval的嫌疑,但是实现方式还是值得借鉴的
[python]
a = int(input(‘input a number:’))
item = {‘a<=10’: ‘a(1+0.1)’, ‘a>10 and a<=20’: ‘10(1+0.1)+(a-10)(1+0.075)’, ‘a>20 and a<40’: ‘(a-20)(1+0.05)’,’40<a<=60’: ‘(a-40)(1+0.03)’, ‘60<a<=100’: ‘(a-60)(1+0.15)’, ‘a>100’: ‘(a-100)*(1+0.01)’}
for i in item.keys():
if eval(i):
print(eval(item[i]))
[/python]

Python每日一题 第1期

这两天参加的一个群里开展了每日一题的活动,虽然题目简单了点,但是拿来练练手也挺不错的。

【我们一起学Python吧】每日一题 第1期
题目:有1、2、3、4个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?
[python]
count = 0
for i in range(1, 4 ** 3):
a = i % 4
b = int(i / 4) % 4
c = int(i / 16)
if (a != b) & (a != c) & (b != c):
count += 1
print(str(c + 1) + str(b + 1) + str(a + 1))
print(‘total:%d’ % count)
[/python]

这是群中高手的解法,用到了列表解析。
[python]
lis = {1, 2, 3, 4}
l = [x 100 + y 10 + z for x in lis for y in lis - {x} for z in lis - {x} - {y}]
print(len(l), l)
[/python]

python3学习——注册机的实现

前言

这个注册机本来是用java写的的,但是在完成后收到了新的需求————添加gui。作为一个被java gui坑过一次的人,我绝不愿意再被坑第二次,而我手头又没装vs,所以便选择据说开发效率极高的python。
一边学一边做,开发过程虽说磕磕碰碰但是最后也算是顺利完成了,贴到网上,既是分享也是备忘。

整个项目主要用到了tkinter,pyDes,rsa,multiprocessing等库。tkinter用于实现gui。pyDes用于对key文件加密。multiprocessing用于实现多线程素性测试以提高rsaKey的生成效率。而最关键的则是rsa库了,但是事实上最后的版本中是没有引入rsa库的,原因是rsa库在打包过程中出现了问题,所以我参考rsa库和网上的一篇论文实现了一个简单myrsa库。

模块

main函数

一个相当简陋的对话框带着四个同样简陋的按钮。
生成授权:接受用户输入的授权信息包括机器码,授权开始结束日期等
选择密钥:选择授权时所使用的key
创建密钥:随机生成rsaKey
导出公钥:导出rsa算法中的e和n,这两个参数需要写死在被保护的程序的验证模块中
[python]
if name == ‘main‘:
root = Tk()
Button(root, text=’生成授权’, relief=RAISED, command=register).pack()
Button(root, text=’选择密钥’, relief=RAISED, command=select_key).pack()
Button(root, text=’创建密钥’, relief=RAISED, command=create_key).pack()
Button(root, text=’导出公钥’, relief=RAISED, command=get_pubkey).pack()
root.mainloop()
[/python]

创建密钥

首先接受用户输入的密钥位数和密钥别名,然后调用myrsa库中的newkeys生成密钥,myrsa会根据当前机器的cpu核心数量开启相应数量的线程进行计算。「此处没有对length最大最小值进行约束,存在安全隐患」
密钥生成完毕后会调用save_key进行保存。程序将在key文件下下根据用户输入的别名生成一个文件夹,然后用文件头部的_des_key对密钥对加密,最后保存为pub.key和pri.key。加入des算法是因为注册机与key文件分离,为了防止key文件流出导致第三方任意授权所以对key进行加密。加入des后必须有key文件保存时所用的_des_key否则无法读取key文件。
此外,程序还会向同目录下生成一个xml文件保存key的别名、位数和kid,其中kid可写入被保护程序,用于判断授权文件所使用的key。
[python]
def create_key():
length = simpledialog.askinteger(root, "请输入密钥长度")
remark = simpledialog.askstring(root, "输入密钥名称")
(pubkey, prikey) = newkeys(length)
config = configparser.ConfigParser()
config.add_section(‘info’)
config.set(‘info’, ‘length’, str(length))
save_key(pubkey, prikey, config, remark)
messagebox.showinfo("", "密钥生成完毕")

def save_key(pubkey, prikey, config, remark):
pub = pubkey.save_pkcs1()
pri = prikey.save_pkcs1()
encrypter = des("DESCRYPT", CBC, _des_key, pad=None, padmode=PAD_PKCS5)

time_stamp = int(time.time() * 1000)
if os.path.exists('key/' + remark):
    i = 1
    while os.path.exists('key/' + remark + '_' + str(i)):
        i += 1
    remark = remark + '_' + str(i)
config.set('info', 'kid', str(time_stamp))
e_pub = encrypter.encrypt(pub)
e_pri = encrypter.encrypt(pri)

file_prefix = 'key/' + remark
os.makedirs(file_prefix)
with open(file_prefix + '/info.ini', 'w') as f:
    config.write(f)
with open(file_prefix + '/pub.key', 'wb') as f:
    f.write(e_pub)
with open(file_prefix + '/pri.key', 'wb') as f:
    f.write(e_pri)

[/python]

选择密钥

之前的控制台版可以列出所有密钥进行选择,gui版本只做了根据用户输入寻找key文件夹然后写入配置文件。
[python]
def select_key():
key_dir = filedialog.askdirectory()
if None == key_dir:
return
config = configparser.ConfigParser()
config.read(‘config.ini’)
if not config.has_section(‘info’):
config.add_section(‘info’)
config.set(‘info’, ‘current_key’, key_dir)
with open(‘config.ini’, ‘w’) as f:
config.write(f)
[/python]

导出公钥

这里和上面的选择密钥原本在控制台版中都属于密钥管理的功能,为了适应gui做了弱化,会读取当前选择的key然后将kid,n,e写入指定的配置文件。
[python]
def get_pubkey():
key_dir = filedialog.askdirectory()
(pubkey, prikey, config) = load_key(key_dir)
name = filedialog.asksaveasfilename(defaultextension=’.ini’, initialfile=’pubkey’,
initialdir=’’, filetypes=[(‘公钥’, ‘.ini’)])
pubkey_cfg = configparser.ConfigParser()
pubkey_cfg.add_section(‘pubkey’)
pubkey_cfg.set(‘pubkey’, ‘kid’, config.get(‘info’, ‘kid’))
pubkey_cfg.set(‘pubkey’, ‘n’, str(getattr(pubkey, ‘n’)))
pubkey_cfg.set(‘pubkey’, ‘e’, str(getattr(pubkey, ‘e’)))
with open(name, ‘w’) as f:
pubkey_cfg.write(f)
[/python]

生成授权

首先会读取配置文件获得当前的key,然后接受用户输入的授权信息并拼接成json串,最后调用_encrypt加密。加密完后会进行解密与原信息比对如果无法通过校验会提示密钥可能失效。
最后新建一个二进制文件在前8位写入kid,然后写入加密后的授权信息
[python]
def register():
if not os.path.exists(‘config.ini’):
select_key()
print(1)
config = configparser.ConfigParser()
config.read(‘config.ini’)
try:
current_key = config.get(‘info’, ‘current_key’)
print(current_key)
load_key(current_key)
print(3)
except Exception:
select_key()
current_key = config.get(‘info’, ‘current_key’)

code = simpledialog.askstring(root, '请输入机器码')
begin_str = simpledialog.askstring(root, '请输入授权开始日期(yyyy-MM-dd)')
end_str = simpledialog.askstring(root, '请输入授权结束日期(yyyy-MM-dd)')
count = simpledialog.askstring(root, '请输入客户端数量')
name = filedialog.asksaveasfilename(defaultextension='.license', initialdir='', initialfile=str(int(time.time())),
                                    filetypes=[('授权文件', '.license')])
reg_info = {}
if not None == code:
    reg_info['code'] = code
if not None == begin_str:
    begin_date = time.strptime(begin_str, &quot;%Y-%m-%d&quot;)
    reg_info['beginDate'] = int(time.mktime(begin_date) * 1000)
if not None == end_str:
    end_date = time.strptime(end_str, &quot;%Y-%m-%d&quot;)
    reg_info['endDate'] = int(time.mktime(end_date) * 1000)
if not None == count:
    reg_info['count'] = count
json = JSONEncoder().encode(reg_info)

(pubkey, prikey, config) = load_key(current_key)
e = getattr(prikey, 'e')
d = getattr(prikey, 'd')
setattr(prikey, 'e', d)
setattr(prikey, 'd', e)
setattr(pubkey, 'e', d)
e_json = _encrypt(json.encode(), pubkey)
d_json = _decrypt(e_json, prikey)

if not json.encode() == d_json:
    print(&quot;校验失败,密钥可能损坏。请确认license是否有效&quot;)

with open(name, 'wb') as f:
    f.write(struct.pack('&gt;q', int(config.get('info', 'kid'))))
    f.write(e_json)

[/python]

验证部分

关于被保护的程序中的验证部分这里以java举例,需要将导出公钥中得到的e,d,kid以常量形式写入程序,如果对不同版本需要隔离只需要修改常量即可。
在验证部分会读取指定文件,然后读取前八位与常量kid比较如果不同则认为授权与软件版本不匹配,如果相同则利用常量n和e生成公钥对剩余部分进行解密。解密后得到授权信息开始对机器码等参数进行验证并返回结果。
[java]
private static final String modulus = "";
private static final String publicExponent = "65537";
private static final long kid =
L;

try (DataInputStream in = new DataInputStream(new BufferedInputStream(
new FileInputStream(file)))) {

Long licenseKid = in.readLong();
if (kid != licenseKid) {
    return false;
}
byte[] license = new byte[(int) file.length() - 8];
in.read(license);

RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(&quot;RSA&quot;)
    .generatePublic(
        new RSAPublicKeySpec(new BigInteger(modulus),
            new BigInteger(publicExponent)));

String licenseJsonString = RSAUtil.decryptByPublicKey(
    RSAUtil.bcd2Str(license), pubKey);
JSONObject jsonObject = (JSONObject) new JSONParser()
    .parse(licenseJsonString);

String code = (String) jsonObject.get(&quot;code&quot;);
if (null != code &amp;&amp; !getSigarSequence().equals(code)) {
    return false;
}
Long beginDate = (Long) jsonObject.get(&quot;beginDate&quot;);
if (null != beginDate &amp;&amp; beginDate &gt; new Date().getTime()) {
    return false;
}
Long endDate = (Long) jsonObject.get(&quot;endDate&quot;);
if (null != endDate &amp;&amp; endDate &lt; new Date().getTime()) {
    return false;
}
Object o = jsonObject.get(&quot;count&quot;);
Integer count = o instanceof String ? Integer.valueOf((String) o)
    : (Integer) o;
if (null != count) {
    //TODO 设置客户端数量
}
return true;

} catch (Exception e) {
e.printStackTrace();
return false;
}
[/java]

总结

总的来说虽然在学习python方面花了不少时间,但是python的开发效率的确很高,对同类元素的高度整合使得python开发的过程中用于很少为了「哪一种方案更好」这种事而浪费时间。虽然执行效率偏低,但是这对于一些轻量级的工具应用而言,省下来的开发时间远比程序执行所花费的时间来的多,的确是开发各种小工具的首选。

另外,上面提到过rsa无法打包的问题,附件中已经用我自己实现的库代替了rsa,源代码下载后可以使用cxfreeze打包为exe文件。

python3实现注册机源码