支付宝无线商户接入遇到的问题总结

标签 : , , ,

最近我在为“星点无线”产品增加手机支付功能,支付当然首先想到的是支付宝,而且也在其他类似无线产品里看到集成支付宝应用。

首先在支付宝无线商户平台里签约,我们分别签了“手机网页支付”和“手机应用内支付”,所在行业的费率会有所不同,请根据实际情况选择,“手机网页支付”和“手机应用内支付”的费率也不同,“手机应用内支付”低一些。

最开始,我只想简单一点,在iOS应用里内嵌Web,采用手机网页支付,服务器端是用PHP,手机网页支付,采用MD5和RSA两种签名方式,MD5相对简单,但安全性不如RSA,我最开始测试MD5,而且之前在网站上集成支付宝,也没啥问题,开始是比较顺利的,到支付宝支付时,支付宝默认选择了短信支付方式(可能和我们签约的分类有关),也就是支付宝会通过手机短信发送一个验证码,然后回复验证码,支付宝再会发送一个短信,短信里包含一个URL,点击这个URL,会提示支付完成,然后转到我们网站的回调地址,我方程序完整成个订单,但支付宝提供的这个URL,有时候打开会报错误,而且也不是每次都错,非常的奇怪,更大的问题是在成功后回调我服务器程序时报签名不对,我仔细检查了配置,没有问题,只能一点点排错,最后发现支付宝提供的Demo包里alipay_function.php的para_filter方法有问题,这个方法会漏掉回传的out_trade_no参数,非常的奇怪,只有修改这个方法为

function para_filter($parameter) {
  $para = array();
  foreach ($parameter as $key=>$val) {
    if($key == "sign" || $key == "sign_type" || $val == "") {
      continue;
    } else {
      $para[$key] = $parameter[$key];
    }
  }
  return $para;
}

签名问题解决,但由于短信支付最后这个URL的诡异表现,我为此专门询问支付宝客户支持,得到的回答是他们也发现这个问题,但不知道是什么原因造成的,技术也解决不了,竟然也有支付宝解决不了的问题啊!

这个问题不能解决,确实对用户的体验不太好,我只能选择“手机应用内支付”,支付宝提供了iOS的集成代码,文档也算详尽,但iOS的签名采用RSA,所以在服务器端先要改为RSA的(RSA的alipay_function.php para_filter方法有同样的问题),生成公钥、私钥的方法在文档里比较清楚,就是注意上传商户公钥时一定要删除文件头“—–BEGIN PUBLIC KEY—–”与文件尾“—–END PUBLIC KEY—–”还有空格、换行,变成一行字符串并保存为TXT文件,商户私钥在PHP程序不需要转为PKCS8格式,而在其他语言里(比如Objective-C)需要转为PKCS8格式,并在iOS应用里设置plist文件中RSA private key变量时,要把PKCS8格式私钥的换行和头尾的“—–BEGIN PRIVATE KEY—–”与“—–END PRIVATE KEY—–”删除。

在iPhone真机调试过程中,跳到支付宝应用时报签名错误,我又仔细检查了好几遍,确定没什么问题,后来在支付宝的指导下先测试运行他们提供的Demo程序,没有问题,说明签名密钥没问题,肯定是要签名的数据有问题,我对比了一下发现AlixPayOrder中的productDescription不能为空,也就是传输数据中的body变量不能为空,终于完成了无线支付的集成。

支付宝提供的文档还算比较全面,但有些地方还是有些含糊,如果是iOS集成的话,先测试它提供的Demo程序,以确定密钥的正确性,仔细调试,记录Log,还是比较容易集成的。

Nginx下alias支持PHP的问题

标签 : ,

这几天在配置Nginx,PHP用FastCGI,想装一个phpMyAdmin管理数据库,phpMyAdmin不想放在网站根目录下,这样不容易和网站应用混在一起,这样phpMyAdmin的目录就放在别处,在Apache里,有alias,比较方便,在Nginx下没有虚拟目录概念的,是用location配合alias使用,我先试了简单的配置方式

location /web/ {
alias  /data/web/;
index  index.html index.htm index.php;
}

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

我用http://localhost/web/可以访问到/data/web目录下的静态文件,但访问php文件,却报No input file specified.的错误,而且在Nginx的error日志上却什么信息也没有,我在网上搜索了一下,判断应该是php文件并没有被后端的FastCGI运行,我又继续搜索一些文章,试着增加了一段配置

location /web/ {
alias  /data/web/;
index  index.html index.htm index.php;
}

location ~ ^/web/.+\.php$ {
root /data/;
rewrite /web/(.*\.php?) /$1 break;
include fcgi.conf;
fastcgi_pass   127.0.0.1:9000;
fastcgi_index  index.php;
fastcgi_param SCRIPT_FILENAME /data/web$fastcgi_script_name;
}

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

这下可以了,原理应该是采用rewrite的方法,对于/web/下php类型的的请求交给后端的FastCGI处理,并且指定了php脚本的位置,这样我们就可以配置phpMyAdmin了,配置如下

location /phpmyadmin/ {
alias  /data/phpmyadmin/;
index  index.html index.htm index.php;
}

location ~ ^/phpmyadmin/.+\.php$ {
root /data/;
rewrite /phpmyadmin/(.*\.php?) /$1 break;
include fcgi.conf;
fastcgi_pass   127.0.0.1:9000;
fastcgi_index  index.php;
fastcgi_param SCRIPT_FILENAME /data/phpmyadmin$fastcgi_script_name;
}

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

要注意的是

location ~ .*\.(php|php5)?$ {
fastcgi_pass  127.0.0.1:9000;
fastcgi_index index.php;
include fcgi.conf;
}

这段,要放在phpmyadmin的后面,放在前面就有问题,这是和Nginx的location规则有关,具体看Nginx的文档,另外,phpMyAdmin里要配置一下URI的绝对路径,就可以了。

在hostmonster主机上安装MediaWiki出现的500问题

标签 : , ,

今天在hostmonster主机上安装MediaWiki,按照文档要求把config目录更改权限

chmod a+w config

但做初始化配置时出现了500服务器内部错误,非常奇怪,我搜了一下相关信息,发现是config目录权限有问题,还是改回755,之后安装,就没有问题了。

WordPress升级到2.8遇到的问题

标签 : ,

上周末的时候看到后台提示可以升级到wordpress2.8,我就简单做了一个文章导出,之后在线升级到2.8,我之前的版本是2.7.1,提示升级成功,但转到“控制板”之后,提示错误

Allowed memory size of  xxxxxxxxx bytes exhausted

2.8难道有问题,我可没有做完整的备份,2.8的数据库有改动,如果2.8真的有问题就麻烦了,这个错误是说PHP分配内存太小,只要修改php.ini就可以,但我现在用的是虚拟主机,不能修改php.ini的,还是网上搜一下吧,很快,找到了答案,只要在wp-settings.php里修改为

define(‘WP_MEMORY_LIMIT’, ’64M’);

就可以了(以前是32M,如果不行可以继续改大)。

Memory解决了,但后台还是提示有新版本2.8,要我升级,我不是已经升级成功了吗,奇怪,后来查了一下,由于我用的是中文版,所以在wp-includes/version.php里增加一行

$wp_local_package = ‘zh_CN’;

就不会再提示有新版本了。

PHP中的时间时区运算

标签 :

我在测试twitter的API时,在它返回的用户信息里,关于用户时区的信息有两个

<utc_offset>28800</utc_offset>
<time_zone>Beijing</time_zone>

我希望利用这两个数值,得到用户的的DateTimeZone,直接new DateTimeZone(‘Beijing’)是不行的,我看了PHP的API,时区里是没有“Beijing”的,中国的时区应该是“Asia/Shanghai”,看来这个“Beijing”只是用来显示的,我又查了DateTimeZone的API,里面也没有根据UTC Offset值取得DateTimeZone的方法,看来不能通过这样的方法来做了。

utc_offset这个值是应该表示与UTC标准时间相差的秒数,测试下面的代码

$datetimezone = new DateTimeZone(‘Asia/Shanghai’);
$datetime = new DateTime(“now”,$datetimezone);
echo $datetime->getOffset();

结果显示28800,没错,时区“Asia/Shanghai”与UTC的时间差是28800秒,也就是8个小时(我们在东8区)。

在PHP中使用time()或是DateTime对象,在不指定DateTimeZone时,其默认的DateTimeZone就是UTC,产生的时间就是UTC时间,把这个时间加上Offset值之后,就可以显示出本时区的正确时间了,比如下面的代码

$datetime = time();
echo date(“y-m-d H:i:s”,$datetime);
echo “<br/>”;
$datetime = $datetime + 28800;
echo date(“y-m-d H:i:s”,$datetime);

输入的结果是
09-05-13 04:21:44
09-05-13 12:21:44

上面是UTC的时间,而下面的是北京时间。

在我们设计数据的时候,时间字段设计为长整数字段比较方便,存放UTC的time()的Unix时间戳,用户可以自己定义所在的时区,通过Offset,最终显示给用户其时区的时间。我感觉Java里的TimeZone似乎要更灵活一些,可以这样TimeZone.getTimeZone(“GMT+08:00″)

另外:在PHP里取得所有可用时区的方法是DateTimeZone::listIdentifiers()。

Sphinx+Mysql初使用体验

标签 : , ,

应用越来越多的需要全文搜索技术来支撑,在Java中可以使用Lucene,一个非常优秀的引擎,在Hibernate中也整合了Lucene来做检索,但在使用PHP的过程中迫切需要找一个优秀的全文搜索引擎(虽然也可以把PHP和Lucene结合起来使用,但有些另类,有些生产环境也不能同时支持),以前在网上看到一些Sphinx的文章,一直没有实践,昨天我就花了一天的时间,配置测试了一下Sphinx。

由于我的PHP没有编译Sphinx模块,所以我主要是配置Sphinx+Mysql,在Mysql上测试全文搜索的效果,Mysql、Sphinx、中文分词的编译安装过程不详述,下面两篇文章挺好,安装时可以参考

Mysql+sphinx+中文分词简介

基于Sphinx+MySQL的千万级数据全文检索(搜索引擎)架构设计

我的编译安装过程没有遇到什么麻烦,就是编译Mysql的时候比较长,而且要注意把Innodb的引擎编译进去,我的Mysql编译参数如下

./configure –with-plugins=sphinx,innobase,heap –prefix=/usr/local/mysql –enable-assembler –with-charset=utf8 –with-extra-charsets=all –enable-thread-safe-client –with-big-tables –with-readline –with-ssl –with-embedded-server –enable-local-infile

比较奇怪的是innodb是支持了,heap不支持,这个问题下次再研究。

通过编译Mysql,使Mysql支持了Sphinx存储引擎,试了一下Sphinx的例子,成功搜索到了数据,如果我们要对自己的数据做索引,就要研究一下Sphinx的配置文件了。

Sphinx的配置文件在其安装目录下的etc目录下,你可以参考其例子的sphinx.conf创建自己的配置文件,在sphinx里有主要要配置的有两大块,一部分是source(数据源),另一部分是index(索引),source里面定义了连接数据库的参数,取得源数据的SQL,也就是你要索引的数据的取得 SQL(Sphinx是支持不同数据源的,我这里只测试SQL),source可以有继承关系,继承的source可以用来做取得增量数据,index里面定义了使用哪个source,index存放的路径、字符集、辞典等等,index也可以继承,继承的index用来做增量索引。由此可见source 和index都是根据你的需要配置的,可以取得多个数据源的数据,可以建立多个索引。

增量索引的小困惑,我在数据库中增加了2条记录,执行

/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1stemmed

看到有两个文档被加入索引(增量部分),之后我执行

/usr/local/sphinx/bin/indexer –rotate –merge test1 test1stemmed –merge-dst-range deleted 0 0

将增量索引并入主索引,这是可以查询到新插入的数据,这时我再继续插入一条数据,执行

/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1stemmed

提示信息是有3个文档被加入索引,让我非常奇怪,因为上两条纪录已经并入主索引了,这次怎么还会索引呢?之后我执行

/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1

更新主索引,之后再执行/usr/local/sphinx/bin/indexer –rotate –config /usr/local/sphinx/etc/sphinx.conf test1stemmed提示没有索引加入,这样就正确了,如果按照测试的结果,增量索引和主索引更新执行的时间要计划好。

关于中文分词–LibMMSeg:LibMMSeg 是Coreseek.com为 Sphinx 全文搜索引擎设计的中文分词软件包,其在GPL协议下发行的中文分词法,采用Chih-Hao Tsai的MMSEG算法。

同时Coreseek.com提供了一份Sphinx的中文文档,里面有比较详细的配置说明,是很好的参考资料,非常感谢开源人士做出的贡献。

Short Url(短网址)实现方式

标签 : ,

现在提供Short Url(短网址)的网站越来越多了,特别是Twitter的助Short Url的一臂之力,越来越多的人开始使用Short Url-短网址,现在比较流行的几个Short Url网站有

TinyURL
http://tinyurl.com/

Bit.Ly
http://bit.ly/

Is.Gd
http://is.gd/

等等,Short Url的作用在于把长的Url缩成短的Url,比如,我前两天些的一篇博客,Url是http://i.laoer.com/think-about-http-get-chinese-encode-error.html,我们采用TinyURL,转成的Url是http://tinyurl.com/d4zw8x,只有25个字符,短了很多,请求http://tinyurl.com/d4zw8x的时候,tinyurl会把请求通过HTTP 301转到http://i.laoer.com/think-about-http-get-chinese-encode-error.html上。

实现Short Url的功能并不复杂,但最关键的就是这个短代码要够短,而且需要唯一,我们的例子是“d4zw8x”,6位,还有就是用户输入的同一个Url,应该返回唯一的Short Url,用户在请求长的Url之后,先从数据库查找一下这个长Url是否存在,如果存在,就直接取出其对应的短代码,如果不存在,则生成短代码,与用户的长Url同时保存在数据库中。

最核心的这个短代码的实现方式,我在网上找了两个

第一个是纯随机数的算法,来自Short URL Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function random($length, $pool = '')
    {
        $random = '';
 
        if (empty($pool)) {
            $pool    = 'abcdefghkmnpqrstuvwxyz';
            $pool   .= '23456789';
        }
 
        srand ((double)microtime()*1000000);
 
        for($i = 0; $i < $length; $i++) 
        {
            $random .= substr($pool,(rand()%(strlen ($pool))), 1);
        }
 
        return $random;
    }

另一个算法来自http://www.snippetit.com/2009/04/php-short-url-algorithm-implementation/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function shorturl($input) {
  $base32 = array (
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
    'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
    'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
    'y', 'z', '0', '1', '2', '3', '4', '5'
    );
 
  $hex = md5($input);
  $hexLen = strlen($hex);
  $subHexLen = $hexLen / 8;
  $output = array();
 
  for ($i = 0; $i < $subHexLen; $i++) {
    $subHex = substr ($hex, $i * 8, 8);
    $int = 0x3FFFFFFF & (1 * ('0x'.$subHex));
    $out = '';
 
    for ($j = 0; $j < 6; $j++) {
      $val = 0x0000001F & $int;
      $out .= $base32[$val];
      $int = $int >> 5;
    }
 
    $output[] = $out;
  }
 
  return $output;
}

其返回的是一个4个元素的数组,应为存在可能的重复性,你可以依次使用这4个元素。

Short Url的算法应该还有一些,Short Url网站的作用除了缩短网址以外,在使用者不断增加之后,可以积累庞大的网址信息,这对统计分析是很有用的。

Kohana的Cache

标签 : , ,

Kohana里有个Cache Library,我今天就测试了一下。

首先要配置,将system/config下的cache.php复制到application/config下,打开cache.php文件,我们看一下内容

1
2
3
4
5
6
7
$config['default'] = array
(
    'driver'   => 'file',
    'params'   => APPPATH.'cache',
    'lifetime' => 1800,
    'requests' => 1000
);

这是一个默认配置,’driver’为驱动的缓存方式,Kohana支持6种不同的驱动,分别是File、SQlite、Memcache、APC、Eaccelerator、Xcache,配置文件默认使用的file,其原理是,写缓存时把对象序列化写入文件,读缓存时从文件读出文本反序列化,所以在文件方式下,缓存是基于I/O的,在文件多而且大的时候,性能会有下降, ‘params’是驱动参数,在file驱动模式下,就是cache文件路径,’lifetime’是cache的生命周期,单位为秒,超过这个时间,内容将被清除(设置为0代表不自动清除),’requests’为在达到请求数量之前自动垃圾回收。

在应用中,可能需要不止一个缓存,所以可以配置多个缓存,增加$config数组即可,还有在file方式下可以为每个缓存设置单独文件路径,但前提是文件路径要存在,例如我们增加一个

1
2
3
4
5
6
7
$config['my'] = array
(
    'driver'   => 'file',
    'params'   => APPPATH.'cache/my',
    'lifetime' => 1800,
    'requests' => 1000
);

接下来我们在Controller里调用cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Mycache_Controller extends Controller {
 
    public function index() {
        $mychache = Cache::instance("my");
        $mychache->set("name","laoer");
        echo "OK";   
    }
 
    public function name() {
        $mychache= Cache::instance("my");
        $name = $mychache->get("name");
        echo $name;
    }   
}

Cache::instance(“my”)实例化配置里$config['my']的cache,如果用Cache::instance()就是实例化配置里$config['default']的cache,在浏览器里执行,已经可以存取了,在application/cache/my/文件下可以看到一个名为”name~~0″的文件,里面就是序列化的数据。

文件cache还是有一定的局限性,现在越来越的网站开始使用Memcached所谓缓存的解决方案,Kohana的缓存驱动里,有Memcached的支持,但它的文档却没有给出Memcached的配置例子,看来要自己摸索一下。

将system/config下的cache_memcache.php复制到application/config下,cache_memcache.php的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @package  Cache:Memcache
 *
 * memcache server configuration.
 */
$config['servers'] = array
(
	array
	(
		'host' => '127.0.0.1',
		'port' => 11211,
		'persistent' => FALSE,
	)
);
 
/**
 * Enable cache data compression.
 */
$config['compression'] = FALSE;

根据你自己的情况修改Memcached的服务地址和端口,在application/config/cache.php里再加一段

1
2
3
4
5
6
7
$config['mem'] = array
(
	'driver'   => 'memcache',
	'params'   => '',
	'lifetime' => 1800,
	'requests' => 1000
);

在Controller里把Cache::instance(“my”)改为Cache::instance(“mem”),运行看看结果,已经可以从Memcahced里存取了。

Kohana的Memcached驱动还是有些缺陷,现在只能使用一组Memcached,即$config['servers']这个参数,我觉得Memcacahed组也应该是多个,因为从业务角度会根据功能对cache做划分,我大概看了一下system/libraries/drivers/Cache/Memcache.php文件,应该是可以改造的,还有一点,编译PHP的时候要安装Memcached的支持。

PHP SOAP实践

标签 : , ,

我现在公司里的系统都是SOA/面向服务架构的,用Java编写的Web Service,理论上Web Service支持异构语言的,我想测试一下用PHP来调用Java的Web Service是否方便。

首先用Java建立一个Web Service服务端(用CXF,JAX-WS实现),下面是公布出来的WSDL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version='1.0' encoding='UTF-8'?><wsdl:definitions name="SampleWebService" targetNamespace="http://www.abc.net/WS/2008" xmlns:ns1="http://webservice.passport.abc.net/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.abc.net/WS/2008" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <wsdl:import location="http://192.168.19.42:8080/sample/service/SampleWebService?wsdl=SampleWebService.wsdl" namespace="http://webservice.passport.abc.net/">
    </wsdl:import>
  <wsdl:message name="helloUser">
    <wsdl:part element="ns1:helloUser" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:message name="helloUserResponse">
    <wsdl:part element="ns1:helloUserResponse" name="parameters">
    </wsdl:part>
  </wsdl:message>
  <wsdl:binding name="SampleWebServiceSoapBinding" type="ns1:SampleWebService">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="helloUser">
      <soap:operation soapAction="" style="document" />
      <wsdl:input name="helloUser">
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output name="helloUserResponse">
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="SampleWebService">
    <wsdl:port binding="tns:SampleWebServiceSoapBinding" name="SampleWebServicePort">
      <soap:address location="http://192.168.19.42:8080/sample/service/SampleWebService" />
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

这个Web Service有个“helloUser”方法,传入一个参数,它返回一段文本“Hello 参数!”,

PHP5之后已经加入了SOAP的支持,我最开始用PHP5的SOAP函数来调用,下面是代码

1
2
$client = new SoapClient('http://192.168.19.42:8080/sample/service/SampleWebService?wsdl');
var_dump($client->__getFunctions());

但报错Fatal error: Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: <message> ‘helloUser’ already defined in …,我搜了PHP网站上的相关文档也没找到答案,没办法,我只能找其他SOAP的组件,找到了NuSOAP,它最近发布的版本是0.7.3,是在2007年发布的,现在已经2009了,用用试试吧,参考nusoap的例子,写好自己代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
require_once('nusoap.php');
$client = new nusoap_client('http://192.168.19.42:8080/sample/service/SampleWebService?wsdl', 'wsdl');
$err = $client->getError();
if ($err) {
	echo '<h2>Constructor error</h2><p>' . $err . '</p>';
}
$param = array('arg0'=>'laoer');
$result = $client->call('helloUser', array('parameters' => $param),"http://webservice.passport.abc.net/",'', false, false,"document","literal");
 
if ($client->fault) {
	echo '<h2>Fault</h2><p>';
	print_r($result);
	echo '</p>';
} else {
	// Check for errors
	$err = $client->getError();
	if ($err) {
		// Display the error
		echo '<h2>Error</h2><p>' . $err . '</p>';
	} else {
		// Display the result
		echo '<h2>Result</h2><p>';
		//print_r($result);
		echo $result["return"];
		echo '</p>';
	}
}
 
echo '<h2>Request</h2><p>' . htmlspecialchars($client->request, ENT_QUOTES) . '</p>';
echo '<h2>Response</h2><p>' . htmlspecialchars($client->response, ENT_QUOTES) . '</p>';

执行结果

Result
Hello laoer!

Request
POST /sample/service/SampleWebService HTTP/1.0
Host: 192.168.19.42:8080
User-Agent: NuSOAP/0.7.3 (1.114)
Content-Type: text/xml; charset=UTF-8
SOAPAction: “”
Content-Length: 436

我们可以看到Request的SOAP文本

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://webservice.passport.abc.net/">
	<SOAP-ENV:Body>
		<helloUser xmlns="">
			<arg0 xmlns="">laoer</arg0>
		</helloUser>
	</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

Response
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
SOAPAction: “”
Content-Type: text/xml;charset=UTF-8
Content-Length: 237
Date: Fri, 27 Mar 2009 04:27:11 GMT
Connection: close

Response的SOAP文本

1
2
3
4
5
6
7
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
	<soap:Body>
		<ns2:helloUserResponse xmlns:ns2="http://webservice.passport.abc.net/">
			<return>Hello laoer!</return>
		</ns2:helloUserResponse>
	</soap:Body>
</soap:Envelope>

碰到一个小问题就是PHP给方法传参数,看是我不知道怎么填,后来我用soapUI,做了一个SOAP Request的例子,发现参数的Name是arg0,如果是多个参数,则arg0、arg1、arg2…以此类推,所以我们定义的参数是$param = array(‘arg0′=>’laoer’),还有就是nusoap里对返回信息的编码有点问题,如果你Web Service放回的编码是UTF-8,则nusoap多转换了一次,在nusoap.php文件nusoap_client类里把$decode_utf8置为false就可以了。

PHP RSA研究

标签 : ,

最近研究了一下QQ邮箱的登录过程,发现QQ邮箱登录比较严谨,加了一些小技巧,其中一点就是用JavaScript对用户的密码做了一下RSA的加密,在它的登录页面里有一段

1
2
3
4
5
6
7
8
var PublicKey = "CF87D7B4C864F4842F1D337491A48FFF54B73A17300E8E42FA365420393AC0346AE55D8AFAD975DFA175FAF0106CBA81AF1DDE4ACEC284DAC6ED9A0D8FEB1CC070733C58213EFFED46529C54CEA06D774E3CC7E073346AEBD6C66FC973F299EB74738E400B22B1E7CDC54E71AED059D228DFEB5B29C530FF341502AE56DDCFE9";
var RSA = new RSAKey();
RSA.setPublic(PublicKey, "10001");
var Res = RSA.encrypt(document.form1.pp.value + '\n' + document.form1.ts.value + '\n');
if (Res)
{
document.form1.p.value = hex2b64(Res);
}

再看看RSAKey的相关源码,应该是在http://m367.mail.qq.com/zh_CN/htmledition/js/safeauth.js里,看了一下还是蛮复杂的,我就在Google上搜一下有没有相关的资料,找到了这个网站《BigIntegers and RSA in JavaScript》,看了一下他们的代码,和QQ里的基本一样嘛,估计QQ也是用了人家的代码,这篇文章里有密钥对的生成、加密解密的测试页面,由此推断QQ代码里的PublicKey,应该是密钥对的modulus(也可以理解为公钥吧),并且QQ用的是1024位的密钥,那我们是否可以用这个modulus,使用别的语言(比如PHP)来做密码加密呢?

想到就做,我在Google上搜索一下“PHP RSA”,找到了这个网站http://www.edsko.net/misc/,里面有PHP RSA的实现,不过我对它的rsa_encrypt($message, $public_key, $modulus, $keylength)方法产生了疑惑,它有4个参数,第一个是要加密的字串,那后面三个怎么填,我们现在仅知道的就是modulus了,而$public_key和$keylength从何而来呢,真有点摸不着头脑了,还是看看它带的例子吧,原来它例子里是从密钥对文件中取得相关的信息,QQ的密钥对文件自然取不到,那我只能自己生成一个密钥对文件,来看看有什么规律吧,在Linux下用openssl做

openssl genrsa -out key.pem 1024

生成了一个1024位的密钥对文件,可以打开看看,里面应该是Base64编码的,之后我们通过下面的命令可以得到modulus

1
2
openssl rsa -in key.pem -noout -modulus
Modulus=D192471B8699640F931FE6F4FACC3E990B894F894CEA5BEE0DCBD7A4B76752F7345CF9B5F1271001B724F7A0ABF0A6E911E309536F4BE4749E92DCC531B8E36B95969D206649C9DD2371B413A8DFD9B92569660B1499A5CD310B86A8FDE24988E456897A416D2E7B0B649F0714F322C57EF92563B21A448D1072FF3806C34C75

比照QQ的,位数是一样的,接下来我们用命令

openssl rsa -in key.pem -text -noout

输出的内容如下:
Private-Key: (1024 bit)
modulus:
00:d1:92:47:1b:86:99:64:0f:93:1f:e6:f4:fa:cc:
3e:99:0b:89:4f:89:4c:ea:5b:ee:0d:cb:d7:a4:b7:
67:52:f7:34:5c:f9:b5:f1:27:10:01:b7:24:f7:a0:
ab:f0:a6:e9:11:e3:09:53:6f:4b:e4:74:9e:92:dc:
c5:31:b8:e3:6b:95:96:9d:20:66:49:c9:dd:23:71:
b4:13:a8:df:d9:b9:25:69:66:0b:14:99:a5:cd:31:
0b:86:a8:fd:e2:49:88:e4:56:89:7a:41:6d:2e:7b:
0b:64:9f:07:14:f3:22:c5:7e:f9:25:63:b2:1a:44:
8d:10:72:ff:38:06:c3:4c:75
publicExponent: 65537 (0×10001)
privateExponent:
00:83:d3:d9:08:f6:95:3c:bd:13:56:29:09:07:4e:
3d:3e:36:64:8c:74:98:be:7f:4f:72:bc:3c:0c:f0:
15:7d:b9:e4:e5:6b:6a:c8:a4:42:cc:61:71:4e:97:
72:30:f2:3d:80:33:e9:a4:e3:48:c1:0f:9e:c4:51:
3d:75:f6:90:8e:f3:c3:f8:ce:45:59:2a:67:42:a8:
c6:d0:4c:1d:12:c4:cf:53:f8:b1:58:b4:e1:23:71:
0e:e9:e9:e0:40:3d:9a:99:e3:5f:e1:93:04:e2:0a:
60:34:77:56:be:f9:8f:e6:4e:87:23:46:48:ba:38:
9d:dd:46:ce:20:b7:82:27:cd
prime1:
00:ee:a9:e4:70:9c:d4:fe:bf:cd:87:5c:00:cb:ea:
ef:82:92:e1:88:f7:99:6a:42:09:f4:fd:78:93:bd:
30:28:1f:2e:ed:c1:cd:d3:60:8b:34:52:89:a7:ac:
98:37:cd:96:81:1e:57:2f:46:08:0e:8d:fb:13:92:
8d:f5:7a:50:5f
prime2:
00:e0:cb:65:5e:31:f2:3b:c0:7f:93:ae:d9:6c:35:
75:e5:ce:8b:37:7d:39:ce:82:dd:9b:43:00:09:a6:
d8:c1:ab:bc:10:fe:3d:56:34:fe:bd:38:fe:fc:6c:
f2:74:a8:d6:40:25:e5:5a:35:7b:d0:24:71:44:8d:
53:23:71:83:ab
exponent1:
4b:d5:7f:d8:a8:7c:a5:55:9c:a0:de:03:02:c8:6b:
c2:39:99:a0:43:cc:63:8f:08:4a:e8:1f:60:12:45:
32:fa:75:96:e6:75:d8:2c:5d:0f:0b:0a:e2:54:5d:
29:9e:11:ac:85:4f:7e:9d:ea:01:75:eb:c9:94:4f:
b7:28:5e:51
exponent2:
00:9b:9f:d4:56:a8:e7:55:3c:88:55:fa:97:a5:55:
41:80:ce:44:0d:2f:51:a4:c9:6e:97:fd:83:7a:2b:
1b:26:c1:38:da:de:d8:21:e5:60:72:29:92:45:b9:
3b:05:4e:99:bd:21:3f:2d:fb:96:f2:db:37:db:48:
a7:c5:02:e2:2f
coefficient:
00:c2:75:38:a5:02:24:39:1e:0e:e9:ec:56:6a:31:
5d:38:82:ca:3e:9b:67:cb:40:7e:7b:2f:91:26:bb:
4e:64:3d:60:53:f1:21:67:8b:b7:af:f8:2e:95:f7:
af:cf:42:75:ab:6c:5c:42:97:42:17:94:17:ff:e0:
b9:cb:c9:e8:6d

通过它例子的代码,我明白了,$public_key应该是1024,$keylength就是65537,$modulus不能直接用这段文字,要先转成BigInteger,再转成文本传进去,BigInteger实现在PEAR里有,http://pear.php.net/package/Math_BigInteger

我们接下来就写程序吧

1
2
3
4
5
6
7
8
9
10
11
include('rsa.php');
include('BigInteger.php');
 
$public = 65537;
$modulus = "D192471B8699640F931FE6F4FACC3E990B894F894CEA5BEE0DCBD7A4B76752F7345CF9B5F1271001B724F7A0ABF0A6E911E309536F4BE4749E92DCC531B8E36B95969D206649C9DD2371B413A8DFD9B92569660B1499A5CD310B86A8FDE24988E456897A416D2E7B0B649F0714F322C57EF92563B21A448D1072FF3806C34C75";
$keylength = 1024;
$modulus_16 = new Math_BigInteger($modulus,16);
$mend = $modulus_16->toString();
 
$encrypted = rsa_encrypt("test", $public, $mend, $keylength);
echo bin2hex($encrypted); //这里也可以用Base64,QQ就是Base64

最后说一下我对于RSA的理解,首先生成了公钥/私钥的密钥对,之后把公钥发布出去,外部系统用公钥加密,传给内部系统用私钥解密。