该文基于我早前写 GKiu 时对 GNOME 密钥环的学习。
代码全部经过实测、优化,请放心阅读本文!
GNOME-Keyring 之前已经被 libsecret 取代了,以下内容仅作参考。
GNOME 密钥环是什么?
简单来说,就是存你密码的地方。假如吧,你在好多地方都办理了VIP卡,你总不能天天带在身上吧?而且还很麻烦,于是你就又买了一个保险柜,把你所有的VIP卡、钥匙、银行卡信用卡充值卡……都塞到这个保险柜里面了,你只需要一个密码就能打开找到自己需要的卡片钥匙。GNOME密钥环就是这么做的,它用一个密码锁住所有密码,于是你就不需要记住很多密码,只需要记住一个密码就全部可以了。需要放在正文里面强调的是强调的是,你用很多种不一样的密码正是因为安全原因,现在你正巧用了一个密码,所以骇客只需要获取你这一个密码你可就全线崩溃了,所以必须做好安全工作。当然,这是在本地存储的,所以要截获密码并不是那么容易。
GNOME 密钥环的一些问答
Q:我的系统有 GNOME 密钥环吗?
A:一般来说,只要你的桌面环境是 GNOME 就有。当然如果你有 KDE,安装了一些使用了这个库的软件,也会有。
Q:我需要手动使用它吗?
A:完全不需要,这个东西是给程序用的,所以都会自动(在你的选项下,也许)完成。
Q:如何调教密钥环?
A:呃。。。好吧,你可以用心灵感应遥控它,工具是强大到逆天的
seahorse
,GNOME官方出品,质量有保证;还有出厂时候附赠的小刀一把,直拿软肋,名字就叫gnome-keyring
,不过因为是小刀有些人不会用(警察也不会让你用的),所以不太受欢迎。
如何在自己的程序里面使用 GNOME 密钥环?
这就是我们要谈论的重点了!因为是首次接触 GNOME 密钥环(也许你是),就算你以前用过 seahorse
也不一定用它的接口写过软件,所以就不会多做介绍给高级特性了!而且这部分也够绝大多数人用了,也许以后我还会多讲一些 GNOME 密钥环的 API。
如何安装开发包?
因为我是 Fedora 对 Ubuntu/Debian 系的不太了解,于是只给出了 Fedora 的安装方法,Ubuntu/Debian 自己解决吧。
方法:
sudo yum install gnome-keyring-devel
在接下来的实例环节,我们要多次用到 seahorse 来验证我们的代码运行结果,所以也许你还需要安装一下 seahorse。
sudo yum install seahorse
如何编译?
注意 GNOME 密钥环的开发文件偏离了一般实践,它将包命名为 gnome-keyring-1(而不是1.0),因此你需要这样编译:
cc [文件名] `pkg-config --cflags --libs gnome-keyring-1 glib-2.0`
因为 GNOME 密钥环用到了 GLib,因此你也需要链接它。
保存一个密码
现在就要开始学习使用GNOME密钥环库了!首先我们看看如何保存一个密码……
哇噢!这么长的参数列表!别着急,我们一个一个看,首先是这个 schema 参数。。。也许是的,GNOME 很多库都要这些东西,见怪不怪啦!长话短说,shema 就是一个定义,它定义了你这一个密钥环项目都保存了什么。那么。。莫非我们还要自己写一个 schema?!不瞒你说,GNOME库里面很多用schema的都非常复杂的定义!GNOME 密钥环也不例外,因此内部已经定义好了一个 schema,包含如下参数:
- user:用户名。
- server:服务器地址或名字。
- procotol:用户用来连接到服务器的协议。
- domain:域。
- port:端口。
很显然这是一个用在网络上的 schema,而且令人一点也不意外的是,它这个名字就叫 GNOME_KEYRING_NETWORK_PASSWORD。
好了,我们已经干掉了前进路上的一个很大的障碍!继续前进!
keyring,这个很简单了,就是说要存到哪个密钥环里面嘛!不过问题是,这个类型怎么是 const gchar *?呃,其实每个密钥环是用名字标示的,比如你买了好几个一样的保险柜,要区分哪个是不是要贴上一张标签?那我们如何知道系统已有哪些密钥环呢?还要自己创建?还是枚举出一个最合适的已有的?不需要了!默认GNOME密钥环已经有了一个默认的,我们只需要在这里填上一个 NULL 就OK了,为了让代码更加可读(也更长),于是他们又定义了一个为NULL的GNOME_KEYRING_DEFAULT宏!
接下来的已经没什么比较需要讲解了,因为从名字就能看出来是什么意思。如果你细心的话。。你可能已经发现了一个后面带有 _sync 的同名函数,这是干什么的?嗯,其实默认情况下是异步运行的,也就是这个函数会立即返回并创建一个以回调函数为线程函数的线程,于是你就无须等待,这在一些需要大量存取的情况下非常有效,但是,我们一般的小程序只需要存一两次,为什么需要异步?那就用同步函数好了!也就是说,当操作完成后才会返回(当然,为了内容完备,我们把两个都讲解一下)。
但是你可能会疑惑,这个 destroy_data 又是做什么的呢?这就是另外一个需要的函数了,如果你传递了一个非 NULL 的 data(也就是GTK+里面的用户数据),那么函数会调用你指定的这个函数来释放你这个 data 的内存。
现在有了丰富的理论基础,我们先写一个异步版本的密钥环保存实例!
(在我这里似乎没有进入回调函数,这也可能成为我们使用同步版本的一个理由)
#include <stdlib.h> #include <stdio.h> #include <gnome-keyring.h> static void save_cb (GnomeKeyringResult result, gpointer data) { if (result != GNOME_KEYRING_RESULT_OK) { fprintf (stderr, "保存密码出错:%s!\n", gnome_keyring_result_to_message (result)); } else { printf ("保存密码成功!\n"); } } static void free_data_save_cb (gpointer data) { free (data); } void save_password (const char *usr, const char *pwd) { gpointer data = malloc (sizeof (100)); gnome_keyring_store_password (GNOME_KEYRING_NETWORK_PASSWORD, GNOME_KEYRING_DEFAULT, usr, /* display name */ pwd, /* password */ save_cb, data, free_data_save_cb, "user", usr, "server", "http://ekd123.org", NULL); } int main () { save_password ("mike", "123456"); sleep (10); /* 必须sleep,否则程序退出就无法看到效果了 */ return 0; }
在回调函数里,如果出现错误会使用 gnome_keyring_result_to_message
将结果转化为字符串的消息(如 strerror
所做一样)。
有必要强调一下,在 destroy_data 参数后面就是 schema 提供的参数了,你必须是一键一值,最后用 NULL 结尾,但我不明白的是,为什么需要这个,因为搜索查找的都是使用 display_name (也就是在 seahorse 中展示的名字)。确实是 seahorse 显示的名字,但是查找还需要这些 schema 中的参数。
可以看到,这个异步版本的非常复杂,而且要返回结果也是迂回的,所以一般实践还是喜欢用同步版本。因此在这里再给出一份存储密码的同步版本。
#include <stdio.h> #include <gnome-keyring.h> void save_password (const char *usr, const char *pwd) { GnomeKeyringResult r; r=gnome_keyring_store_password_sync (GNOME_KEYRING_NETWORK_PASSWORD, GNOME_KEYRING_DEFAULT, usr, /* display name */ pwd, /* password */ "user", usr, "server", "http://ekd123.org", NULL); if (r != GNOME_KEYRING_RESULT_OK) { fprintf (stderr, "不能存储密码:%s!\n", gnome_keyring_result_to_message (r)); } else { printf ("存储密码成功!\n"); } } int main () { save_password ("mike", "123456"); return 0; }
很明显地,这个版本的代码缩减了许多,而且更易理解,当我按下回车键后,屏幕上显示出了我意料之中的喜讯。
关于运行效率,time可以告诉我们更多:
$ time ./a.out 存储密码成功! ./a.out 0.01s user 0.02s system 3% cpu 0.878 total
对于普通人来说,这点时间根本没有注意到就溜走了,所以并不成什么问题!
如果你真的要刨根问底,一睹真实效果的话,我只能有图有真相地给你答复:
(背景图为未来 Fedora 16 的背景图,个人非常喜欢)
“切,那不过是你用seahorse伪造的而已。”呃,你这么想就大错特错了,因为我还会使用 find_password
来找回这个密码!
寻找一个密码
还是按照惯例,先上异步版本,再上同步版本,为了代码简洁,我会忽略掉用户数据参数。因为 gnome_keyring_find_password 比存储密码的函数简单多了,所以就不会再解释参数意思了。
#include <stdio.h> #include <gnome-keyring.h> static void find_cb (GnomeKeyringResult result, const char *string, gpointer data) { if (result == GNOME_KEYRING_RESULT_OK) { printf ("找到了密码!密码是:%s\n", string); } else { fprintf (stderr, "查找密码失败:%s。\n", gnome_keyring_result_to_message (result)); } } void find (const char *usr) { gnome_keyring_find_password (GNOME_KEYRING_NETWORK_PASSWORD, find_cb, NULL, NULL, "user", usr, "server", "http://ekd123.org", NULL); } int main () { find ("mike"); sleep (1); return 0; }
注意回调函数的第二个参数,是带有 const
的!这意味着你不能任意地 free
它。
看上去还是异常复杂,反对 store_password
的理由同样适用于这里,何不试试同步版本?不论从哪方面来说,同步版本都比异步版本易用(好吧,其实在任何方面,异步都比同步复杂;线程中的同步与这里的意思不同,而且它非常复杂)。
#include <stdio.h> #include <gnome-keyring.h> void find (const char *usr) { gchar *pwd; GnomeKeyringResult r; r=gnome_keyring_find_password_sync (GNOME_KEYRING_NETWORK_PASSWORD, &pwd, "user", usr, "server", "http://ekd123.org", NULL); if (r == GNOME_KEYRING_RESULT_OK) { printf ("找到了密码!密码是:%s\n", pwd); gnome_keyring_free_password (pwd); } else { fprintf (stderr, "没有找到密码。。%s\n", gnome_keyring_result_to_message (r)); } } int main () { find ("mike"); return 0; }
欢快的喜讯传来了,找到了密码!!哈哈,前面那个反对派呢?现在信了吧!不管你信不信,反正我信了!
删除密码
如果你要删除一个密码,也是非常容易的,因为删除API是最简单的API!
看了例子就知道了!按惯例,异步在前。
#include <stdio.h> #include <gnome-keyring.h> static void del_cb (GnomeKeyringResult result, gpointer data) { if (result == GNOME_KEYRING_RESULT_OK) { printf ("成功删除了密码!"); } else { fprintf (stderr, "删除密码失败:%s!\n", gnome_keyring_result_to_message (result)); } } void del (const char *usr) { gnome_keyring_delete_password (GNOME_KEYRING_NETWORK_PASSWORD, del_cb, NULL, NULL, "user", usr, "server", "http://ekd123.org", NULL); } int main () { del ("mike"); sleep (1); return 0; }
是比存储密码简单了许多!但是,对比了下面同步版本,就略显复杂了:
#include <stdio.h> #include <gnome-keyring.h> void del (const char *usr) { GnomeKeyringResult r; r=gnome_keyring_delete_password_sync (GNOME_KEYRING_NETWORK_PASSWORD, "user", usr, "server", "http://ekd123.org", NULL); if (r == GNOME_KEYRING_RESULT_OK) { printf ("成功删除了密码!\n"); } else { fprintf (stderr, "删除密码失败:%s\n", gnome_keyring_result_to_message (r)); } } int main () { del ("mike"); return 0; }
看,这个才算简单的!有图有真相:
在此我还是向大家力荐使用同步版本,一般情况下同步版本带来的好处远大于弊端(除非你有特殊的环境),而且带来的延迟微不足道,调试起来也更加方便(会在完成后才返回)!
其它问题
你可能觉得奇怪,为什么不用输入密码都能查找出来?那岂不是非常不安全?嗯,这个你大可放心,因为这是用你登录密码加密的,所以根本不用担心。但是如果你自己创建一个密钥环,那可就得需要密码啦!当然你可以让用户输入,也可以自己选定,一切由你决策!