当前位置:网站首页>基于OpenPGP的文件管理系统

基于OpenPGP的文件管理系统

2022-07-23 14:58:00 biyezuopinvip

基于 OpenPGP 的文件管理系统

一、实验要求

  • 设计一个本地文件处理协议,基于 open PGP 实现本地加密文件夹:

  • 对目标文件实现对存储者和调阅者的基于 pgp 的真实性认证和文件加密;

  • 上述文件安全性不依赖于本地系统,即本地其他非授权用户(即便是系统管理员)无法以可理解的方式读出该文件夹中文件内容;

  • 对处理过程中可能涉及的临时存储至少实现可靠的敏感信息残留覆盖;

  • 选择 Linux 或 MS windows,实现该协议的一个 C++ 实现实例。包括软件设计文档、源代码及注释、可执行安装包、自测用例和测试分析报告、第三方资源及其说明。

1.1 OpenPGP 的加密与解密原理

OpenPGP 加密过程:随机生成一个的 Key,并通过对称加密算法使用这个 Key 加密数据,最后通过非对称加密算法(RSA)用接收者的公钥加密前者的 Key,得到加密的数据。

在这里插入图片描述

1.2 OpenPGP 的数字签名

数字签名是 OpenPGP 的重要组成部分,数字签名是一个数学过程,与现实世界的签名功能相似,但更严谨、更安全且容易验证。数字签名保证了以下情况:

  1. 验证发送者身份:确认发送者确实是他声称的身份。
  2. 完整性:文件/邮件传输过程中未被更改。
  3. 不可否认:发送者不可否认已发送的文件/邮件。

数字签名的原理:发送者先通过加密散列函数获取数据的哈希,然后使用发送者的私钥加密哈希,得到数字签名。接收者使用发送者的公钥解密数字签名得到一个哈希,并与自己计算的数据的哈希值对比,一致则数字签名有效且数据完整。

在这里插入图片描述

1.3 OpenPGP 的公钥发布与吊销证书

我们知道公钥用于加密,可以公开发布,公钥可以点对点发送,也可以上传到密钥服务器。需要格外注意的是公钥中包含邮箱信息,如果你将公钥发布到密钥服务器(各个公钥服务器会互相同步),那么你将永远无法从密钥服务器上删除你的公钥信息。在某天你忘记密码口令或丢失私钥,你想要从密钥服务器上吊销你的公钥,唯一的补救措施是:你事先生成了吊销证书,使用吊销证书可以吊销公钥证书,使公钥其显示“吊销”字样,但依然无法从密钥服务器上删除公钥信息。

所以务必要谨慎上传公钥,务必生成吊销证书备用。

二、文件处理协议设计

前提条件

在该文件系统内,每个用户有一对公钥和私钥。

用户的公钥是公开的,系统内的所有用户都可以获得其他用户的公钥信息。

用户的私钥是私密的,仅该用户可以访问,其他用户不可访问。

用语说明

签名:签名是为了验证发件人的身份,以及内容的一致性(不被篡改),签名通常用于文件创建者的身份,或校验调阅文件的一致性(不被中间人篡改)等等。

加密和解密:加密和解密即使用密钥对中的公钥加密、私钥解密。如果我们需要将自己的文件加密保存,那么就使用自己的公钥加密、自己的私钥解密。如果我们需要给其他用户授权调阅文件,我们将使用对方的公钥加密文件内容,对方使用自己的私钥解密。加密(或签名并加密)后将得到.gpg 文件。

2.1 存储功能

单用户授权

目标:A 用户想要存储自己的文件,并仅自己拥有调阅权限。

步骤:

  • 用户对文件签名:将文件数据通过 MD5 加密方式获得哈希值,再将哈希值通过 A 用户的私钥加密,附在文件末尾。
  • 用户对文件加密:将第一步得到的文件通过 A 用户的公钥加密保存为.gpg 文件。
  • 多用户授权
  • 目标:A 用户想要存储自己的文件,并保证自己和 B 用户都拥有调阅权限。

步骤:

  • 用户对文件签名:将文件数据通过 MD5 加密方式获得哈希值,再将哈希值通过 A 用户的私钥加密,附在文件末尾。

  • 用户对文件加密:将第一步得到的文件通过 A 用户的公钥和 B 用户的公钥加密保存为.gpg 文件。

2.2 调阅功能

目标:A 用户调阅以.gpg 后缀结尾的文件。

步骤:

  • 用户对文件解密:用自己的私钥解密文件,通过比对私钥的 key id 和加密文件的公钥的 key id 是否相同,相同则解密成功,不同则解密失败。
  • 用户验证签名:用公钥库中的公钥对文件中的签名进行验证,如果验证成功,则输出文件创建者(签名者)的身份。
  • 安全问题

2.3 存储者的真实性认证

用户创建文件必须有用户 A 的身份认证,即 A 用户对文件进行数字签名。在前提条件中,用户的公钥是公开的。假设 B 用户以 A 用户的名义创建文件,则 B 用户使用了 A 用户的公钥对文件进行加密,但由于 B 用户没有 A 的私钥,因此无法对文件进行签名。

在创建文件的过程中,我们的文件系统会进行身份核验,即计算文件的哈希值得到 h1使用 A 用户的公钥解密签名 h2判断是否相等(h1==h2),相等则身份认证通过。

因此,B 用户无法以 A 用户的身份创建文件。

在这里插入图片描述

2.4 调阅者的真实性认证

用户 B 只能够查看用户 B 创建的,以及其他用户授权的文件。在前提假设中,用户的私钥是私密的。假设 B 想查看 A 的文件,那么有两种情况:

用户 A 给用户 B 授权:即用户 A 用自己的公钥和用户 B 的公钥对文件进行加密,因此用户 B 能够用自己的私钥进行解密,则能够查看用户 A 创建的文件。

用户 A 未给用户 B 授权:即用户 A 用自己的公钥对文件进行加密,因此用户 B 不能够用自己的私钥进行解密,则不能够查看用户 A 创建的文件。

因此,用户 B 能否查看文件取决于用户 A 有没有授权给用户 B。

在这里插入图片描述

2.4.1 私钥保护

用户的私钥属于该文件系统的敏感信息。假设,用户 B 拥有了用户 A 的私钥,加之公钥是公开的,则用户 B 就能够伪装成用户 A,从而以 A 的名义存储文件和调阅文件,这对整个系统会造成较大的危害。

因此,我们引入了私钥的密码机制,以 MD5(用户名 + 用户安全序列号)作为识别不同用户的指纹,以该指纹作为解锁私钥的密码。

在这里插入图片描述

我们的密钥文件是以 key.store 的文件格式存储,用记事本打开后为乱码,只有拥有此指纹的用户才能够解锁私钥。

在用户执行 exe 可执行程序的过程中,我们也对用户的私钥信息进行了保护。代码的运行机制是先导出私钥文件,然后调用该文件的生成路径,进行签名或者解密等操作,最后清空原有文件内容和文件长度信息,彻底删除文件并无法恢复。同时,我们使用了 c++ 中的析构函数,在读取完私钥信息后,确保缓冲区信息能够及时删除,无法被其他用户读取到敏感信息。

缓存保护

2.5 动态缓存的定位

我们通过构建数据结构进行缓存空间的保护,由于每次读取文件需要申请一定的缓存空间,我们定义 Allocator 类,使用 Alloc 动态内存申请的方式定义存储空间,通过 Free 函数释放空间:

unsigned long Allocator::Alloc(MapNode map[], unsigned long size) {
    MapNode* pNode;
    unsigned long retIdx = 0;
    /* 若pNode->m_Size == 0则表示已经遍历到结尾 */
    for ( pNode = map; pNode->m_Size; pNode++)
    {   if ( pNode->m_Size >= size )
        {   retIdx = pNode->m_AddressIdx;
            pNode->m_AddressIdx += size;
            pNode->m_Size -= size;
            /* 当前内存正好分配完成,将该MapNode所在位置后面的MapNode都向前移动一个位
            置 */
            if ( pNode->m_Size == 0 )
            {
                MapNode* pNextNode = (pNode + 1);
                for ( ; pNextNode->m_Size; ++pNode, ++pNextNode)
                {   pNode->m_AddressIdx = pNextNode->m_AddressIdx;
                    pNode->m_Size = pNextNode->m_Size;
                }
                pNode->m_AddressIdx = pNode->m_Size = 0;
            }
            return retIdx;
        }
    }
    /* no match found */
    return 0;
}

MapNode map[]变量为操作系统页式管理系统内存分配的基本单元(1024*4K)大小,因此我们同样定义 MapNode 变量作为缓存保护的基本单元。

2.6 动态缓存的保护

我们通过施加“排他锁”的方式实现动态缓存的保护——当该用户在执行加密/解密文件的动态写入的过程时,由于 alloc 函数仅只是创建了该空间地址,对于该地址进行“其他用户无法写入”的保护,但是并没有组织其他用户从该内存当中读取数据。

我们使用排他锁的方式,以屏蔽其他用户从该段缓存空间当中读取敏感信息的可能性——当 alloc 申请的空间在释放之前,屏蔽其他一切其他用户进程对于该程序缓存空间的访问权。我们使用 C++ mutex 库获取内核态下其他进程对于程序申请的空间的访问权限,同时通过排他锁实现保护敏感的缓存信息不被窃取。

#include <mutex>

class XMutex { public:
    XMutex() { lockNums.clear();
    }
    ~XMutex() {
    } void lock()
    { mapMutex.lock();
    // 当前用户id与认证者不匹配
    if (lockNums.find(this_thread::get_id()) == lockNums.end() ||
lockNums[this_thread::get_id()] == 0) 
    { lockNums[this_thread::get_id()] = 1;
    // 先释放mapMutex锁,同时锁死非该用户进程
    mapMutex.unlock(); XMutex.lock();
    }
    // 当前线程可获取过X_mutex
    else
    { lockNums[this_thread::get_id()] += 1; 
        mapMutex.unlock();
    }
}
void unlock() { mapMutex.lock();
    if (lockNums.find(this_thread::get_id()) == lockNums.end() ||
lockNums[this_thread::get_id()] == 0) 
    { mapMutex.unlock();
         cout << "X_mutex lock 非认证用户进程无法访问!!\n";
    } else
{
    // 当前是最后一次调用,需要解锁
    if (lockNums[this_thread::get_id()] == 1) 

2.6.1 验证

我们在 windows 下新建一个新用户 client,首先通过 lonovo 通过 alloc 创建一个缓存空间,通过 system(“pause”);保留当前建立的缓存,在新窗口当中登陆 client 用户,alloc 函数的返回值尝试访问新用户的内存空间,用户 B 用于检测该段内存能够被访问。

用户 A(lonovo)创建地址空间进行 PGP 加密

在这里插入图片描述

2.6.1 alloc 函数返回缓存地址的创建首地址和空间大小

在这里插入图片描述

在新窗口中登陆用户 B(client)

在这里插入图片描述

2.6.1 以新用户 client 的身份访问正在进行加密的 lenovo 进程的地址空间

在这里插入图片描述

由上图可知,左图为 client 用户试图访问的地址空间,为 PA 申请的地址空间 +1,

验证结果
在这里插入图片描述

原网站

版权声明
本文为[biyezuopinvip]所创,转载请带上原文链接,感谢
https://biyezuopin.blog.csdn.net/article/details/125946411