侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

SQL Server储过程加密和解密原理深入分析

数据库  /  管理员 发布于 5年前   535

看到有SQL Server 2000和SQL Server 2005 的存储过程加密和解密的方法,

后来分析了其中的代码,发现它们的原理都是一样的。

后来自己根据实际的应用环境,

编写了两个存储过程,

一个加密存储过程(sp_EncryptObject)

一个解密存储过程(sp_EncryptObject)

它们可以应用于SQL Server中的储过程,函数,视图,以及触发器。
感觉这两个存储过程蛮有意思的,拿来与大家分享;

如果你看过类似的,就当作重温一下也好。 


用于加密的存储过程 (sp_EncryptObject) :
存储过程(sp_EncryptObject)加密的方法是在存储过程,函数,视图的“As”位置前加上“with encryption”;

如果是触发器,就在“for”位置前加“with encryption”。
如果触发器是{ AFTER | INSTEAD OF} 需要修改下面代码"For"位置:
代码如下:

if objectproperty(object_id(@Object),'ExecIsAfterTrigger')=0 set @Replace='As' ; else set @Replace='For ';


存储过程完成代码:
代码如下:

Use master 
Go 
if object_ID('[sp_EncryptObject]') is not null 
Drop Procedure [sp_EncryptObject] 
Go 
create procedure sp_EncryptObject 
( 
@Object sysname='All' 
) 
as
/* 
当@Object=All的时候,对所有的函数,存储过程,视图和触发器进行加密 
调用方法: 
. Execute sp_EncryptObject 'All' 
. Execute sp_EncryptObject 'ObjectName' 
*/ 

begin 
set nocount on 
if @Object <>'All' 
begin 
if not exists(select 1 from sys.objects a where a.object_id=object_id(@Object) And a.type in('P','V','TR','FN','IF','TF')) 
begin 
--SQL Server 2008 
raiserror 50001 N'无效的加密对象!加密对象必须是函数,存储过程,视图或触发器。' 
--SQL Server 2012 
--throw 50001, N'无效的加密对象!加密对象必须是函数,存储过程,视图或触发器。',1 
return 
end 
if exists(select 1 from sys.sql_modules a where a.object_id=object_id(@Object) and a.definition is null) 
begin 
--SQL Server 2008 
raiserror 50001 N'对象已经加密!' 
--SQL Server 2012 
--throw 50001, N'对象已经加密!',1 
return 
end 
end 
declare @sql nvarchar(max),@C1 nchar(1),@C2 nchar(1),@type nvarchar(50),@Replace nvarchar(50) 
set @C1=nchar(13) 
set @C2=nchar(10) 
declare cur_Object 
cursor for 
select object_name(a.object_id) As ObjectName,a.definition 
from sys.sql_modules a 
inner join sys.objects b on b.object_id=a.object_id 
and b.is_ms_shipped=0 
and not exists(select 1 
from sys.extended_properties x 
where x.major_id=b.object_id 
and x.minor_id=0 
and x.class=1 
and x.name='microsoft_database_tools_support' 
) 
where b.type in('P','V','TR','FN','IF','TF') 
and (b.name=@Object or @Object='All') 
and b.name <>'sp_EncryptObject' 
and a.definition is not null 
order by Case 
when b.type ='V' then 1 
when b.type ='TR' then 2 
when b.type in('FN','IF','TF') then 3 
else 4 end,b.create_date,b.object_id 
open cur_Object 
fetch next from cur_Object into @Object,@sql 
while @@fetch_status=0 
begin 
Begin Try 
if objectproperty(object_id(@Object),'ExecIsAfterTrigger')=0 set @Replace='As' ; else set @Replace='For '; 
if (patindex('%'+@C1+@C2+@Replace+@C1+@C2+'%',@sql)>0) 
begin 
set @sql=Replace(@sql,@C1+@C2+@Replace+@C1+@C2,@C1+@C2+'With Encryption'+@C1+@C2+@Replace+@C1+@C2) 
end 
else if(patindex('%'+@C1+@Replace+@C1+'%',@sql)>0) 
begin 
set @sql=Replace(@sql,@C1+@Replace+@C1,@C1+'With Encryption'+@C1+@Replace+@C1) 
end 
else if(patindex('%'+@C2+@Replace+@C2+'%',@sql)>0) 
begin 
set @sql=Replace(@sql,@C2+@Replace+@C2,@C2+'With Encryption'+@C2+@Replace+@C2) 
end 
else if(patindex('%'+@C2+@Replace+@C1+'%',@sql)>0) 
begin 
set @sql=Replace(@sql,@C2+@Replace+@C1,@C1+'With Encryption'+@C2+@Replace+@C1) 
end 
else if(patindex('%'+@C1+@C2+@Replace+'%',@sql)>0) 
begin 
set @sql=Replace(@sql,@C1+@C2+@Replace,@C1+@C2+'With Encryption'+@C1+@C2+@Replace) 
end 
else if(patindex('%'+@C1+@Replace+'%',@sql)>0) 
begin 
set @sql=Replace(@sql,@C1+@Replace,@C1+'With Encryption'+@C1+@Replace) 
end 
else if(patindex('%'+@C2+@Replace+'%',@sql)>0) 
begin 
set @sql=Replace(@sql,@C2+@Replace,@C2+'With Encryption'+@C2+@Replace) 
end 
set @type = 
case 
when object_id(@Object,'P')>0 then 'Proc' 
when object_id(@Object,'V')>0 then 'View' 
when object_id(@Object,'TR')>0 then 'Trigger' 
when object_id(@Object,'FN')>0 or object_id(@Object,'IF')>0 or object_id(@Object,'TF')>0 then 'Function' 
end 
set @sql=Replace(@sql,'Create '+@type,'Alter '+@type) 
Begin Transaction 
exec(@sql) 
print N'已完成加密对象('+@type+'):'+@Object 
Commit Transaction 
End Try 
Begin Catch 
Declare @Error nvarchar(2047) 
Set @Error='Object: '+@Object+@C1+@C2+'Error: '+Error_message() 
Rollback Transaction 
print @Error 
print @sql 
End Catch 
fetch next from cur_Object into @Object,@sql 
end 
close cur_Object 
deallocate cur_Object 
end 
Go 
exec sp_ms_marksystemobject 'sp_EncryptObject' --标识为系统对象 
go


如果SQL Server 2012,请修改下面两个位置的代码。

在SQL Server 2012,建议在使用throw来代替raiserror。
 
解密方法:
解密过程,最重要采用异或方法:
[字符1]经过函数 fn_x(x)加密变成[加密后字符1],

如果我们已知[加密后字符1],反过来查[字符1],可以这样:
[字符1] = [字符2] ^ fn_x([字符2]) ^ [加密后字符1] 


这里我列举一个简单的例子:
代码如下:

--创建加密函数(fn_x) 
if object_id('fn_x') is not null drop function fn_x 
go 
create function fn_x 
( 
@x nchar(1) 
)returns nchar(1) 
as 
begin 
return(nchar((65535-unicode(@x)))) 
end 
go 
declare @nchar_1_encrypt nchar(1),@nchar_2 nchar(1) 
--对字符'A'进行加密,存入变量@nchar_1_encrypt 
set @nchar_1_encrypt=dbo.fn_x(N'A') 
--⒖嫉淖址@nchar_2 
set @nchar_2='x' 
--算出@nchar_1_encrypt 加密前的字符 
select nchar(unicode(@nchar_2)^unicode(dbo.fn_x(@nchar_2))^unicode(@nchar_1_encrypt)) as [@nchar_1] 
/* 
@nchar_1 
A 
*/


[注]: 从SQL Server 2000至 SQL Server 2012 采用异或方法都可以解密
用于解密的存储过程(sp_DecryptObject):
代码如下:

Use master 
Go 
if object_ID('[sp_DecryptObject]') is not null 
Drop Procedure [sp_DecryptObject] 
Go 
create procedure sp_DecryptObject 
( 
@Object sysname, --要解密的对象名:函数,存储过程,视图或触发器 
@MaxLength int=4000 --评估内容的长度 
) 
as 
set nocount on 
/* 1. 解密 */ 
if not exists(select 1 from sys.objects a where a.object_id=object_id(@Object) And a.type in('P','V','TR','FN','IF','TF')) 
begin 
--SQL Server 2008 
raiserror 50001 N'无效的对象!要解密的对象必须是函数,存储过程,视图或触发器。' 
--SQL Server 2012 
--throw 50001, N'无效的对象!要解密的对象必须是函数,存储过程,视图或触发器。',1 
return 
end 
if exists(select 1 from sys.sql_modules a where a.object_id=object_id(@Object) and a.definition is not null) 
begin 
--SQL Server 2008 
raiserror 50001 N'对象没有加密!' 
--SQL Server 2012 
--throw 50001, N'无效的对象!要解密的对象必须是函数,存储过程,视图或触发器。',1 
return 
end 
declare @sql nvarchar(max) --解密出来的SQL语句 
,@imageval nvarchar(max) --加密字符串 
,@tmpStr nvarchar(max) --临时SQL语句 
,@tmpStr_imageval nvarchar(max) --临时SQL语句(加密后) 
,@type char(2) --对象类型('P','V','TR','FN','IF','TF') 
,@objectID int --对象ID 
,@i int --While循环使用 
,@Oject1 nvarchar(1000) 
set @objectID=object_id(@Object) 
set @type=(select a.type from sys.objects a where a.object_id=@objectID) 
declare @Space4000 nchar(4000) 
set @Space4000=replicate('-',4000)
/* 
@tmpStr 会构造下面的SQL语句 
alter trigger Tr_Name on Table_Name with encryption for update as return /**/ 
alter proc Proc_Name with encryption as select 1 as col /**/ 
alter view View_Name with encryption as select 1 as col /**/ 
alter function Fn_Name() returns int with encryption as begin return(0) end/**/ 
*/ 

set @Oject1=quotename(object_schema_name(@objectID))+'.'+quotename(@Object) 
set @tmpStr= 
case 
when @type ='P ' then N'Alter Procedure '+@Oject1+' with encryption as select 1 as column1 ' 
when @type ='V ' then N'Alter View '+@Oject1+' with encryption as select 1 as column1 ' 
when @type ='FN' then N'Alter Function '+@Oject1+'() returns int with encryption as begin return(0) end ' 
when @type ='IF' then N'Alter Function '+@Oject1+'() returns table with encryption as return(Select a.name from sys.types a) ' 
when @type ='TF' then N'Alter Function '+@Oject1+'() returns @t table(name nvarchar(50)) with encryption as begin return end ' 
else 'Alter Trigger '+@Oject1+'on '+quotename(object_schema_name(@objectID))+'.'+(select Top(1) quotename(object_name(parent_id)) from sys.triggers a where a.object_id=@objectID)+' with encryption for update as return ' 
end 
set @tmpStr=@tmpStr+'/*'+@Space4000 
set @i=0 
while @i < (ceiling(@MaxLength*1.0/4000)-1) 
begin 
set @tmpStr=@tmpStr+ @Space4000 
Set @i=@i+1 
end 
set @tmpStr=@tmpStr+'*/' 

set @imageval =(select top(1) a.imageval from sys.sysobjvalues a where a.objid=@objectID and a.valclass=1) 
begin tran 
exec(@tmpStr) 
set @tmpStr_imageval =(select top(1) a.imageval from sys.sysobjvalues a where a.objid=@objectID and a.valclass=1) 
rollback tran 

set @tmpStr=stuff(@tmpStr,1,5,'create') 
set @sql='' 
set @i=1 
while @i'' 
begin 
set @patindex=patindex('%'+char(13)+char(10)+'%',@sql) 
if @patindex >0 
begin 
print substring(@sql,1,@patindex-1) 
set @sql=stuff(@sql,1,@patindex+1,'') 
end 
else 
begin 
set @patindex=patindex('%'+char(13)+'%',@sql) 
if @patindex >0 
begin 
print substring(@sql,1,@patindex-1) 
set @sql=stuff(@sql,1,@patindex,'') 
end 
else 
begin 
set @patindex=patindex('%'+char(10)+'%',@sql) 
if @patindex >0 
begin 
print substring(@sql,1,@patindex-1) 
set @sql=stuff(@sql,1,@patindex,'') 
end 
else 
begin 
print @sql 
set @sql='' 
end 
end 
end 
end 
Go 
exec sp_ms_marksystemobject 'sp_DecryptObject' --标识为系统对象 
go


如果SQL Server 2012,请修改下面两个位置的代码。

方法类似于前面的加密过程:

搭建测试环境: 
在一个测试环境中(DB: Test),

先执行上面的加密存储过程(sp_EncryptObject)和解密存储过程(sp_EncryptObject);

再创建两个表:TableA & TableB
代码如下:

use test 
go 
--创建表: TableA & TableB 
if object_id('myTableA') is not null drop table myTableA 
if object_id('myTableB') is not null drop table myTableB 
go 
create table myTableA (ID int identity,data nvarchar(50),constraint PK_myTableA primary key(ID)) 
create table myTableB (ID int ,data nvarchar(50),constraint PK_myTableB primary key(ID)) 
go


接下来,我们要创建6个未加密的对象(对象类型包含 'P','V','TR','FN','IF','TF'):
1.视图(myView):
代码如下:

if object_id('myView') is not null drop view myView 
go 
create view myView 
As 
select * from TableA; 
go



2.触发器(MyTrigger):
代码如下:

if object_id('MyTrigger') is not null drop Trigger MyTrigger 
go 
create trigger MyTrigger 
on TableA 
for update 
As 
insert into TableB(ID,data) select a.ID,a.Data From Inserted a 
go


3.存储过程(MyProc):
代码如下:

if object_id('MyProc') is not null drop proc MyProc 
go 
create proc MyProc 
( 
@data nvarchar(50) 
) 
As 
insert into TableA(data) values(@data) 
go


4.用户定义表值函数(TF)(MyFunction_TF):
代码如下:

if object_id('MyFunction_TF') is not null drop function MyFunction_TF 
go 
create function MyFunction_TF 
( 
) 
returns @t table 
( 
id int, 
data nvarchar(50) 
) 
As 
begin 
insert @t(id,data) select id,data from TableA 
return 
end 
go


5.内联表值函数(IF) (MyFunction_IF):
代码如下:

if object_id('MyFunction_IF') is not null drop function MyFunction_IF 
go 
create function MyFunction_IF 
( 
) 
returns table 
As 
return(select top(3) id, data from TableA order by id desc) 
go


6.标量函数(FN)(MyFunction_FN):
代码如下:

if object_id('MyFunction_FN') is not null drop function MyFunction_FN 
go 
create function MyFunction_FN 
( 
) 
returns nvarchar(50) 
As 
begin 
return(select top(1) data from TableA order by id desc) 
end 
go


当执行完了上面的1-6步骤的脚本,我们通过查询系统视图sys.sql_modules,

可以看到未加密前的定义信息:
代码如下:

select b.name as object,b.type,a.definition 
from sys.sql_modules a 
inner join sys.objects b on b.object_id=a.object_id 
where b.create_date>=convert(date,getdate()) 
order by b.object_id


加密测试:
下面我就通过调用加密存储过程(sp_EncryptObject),一次性对它们进行加密:
代码如下:

use test 
go 
exec sp_EncryptObject 'all' 
go


当我们再查回系统视图sys.sql_modules,会发现definition列返回的是null值

说明定义内容已经给加密:


解密测试:
解密过程,必须在DAC连接SQL Server,

我们这里例子是从 SSMS(SQL Server Management Studio) 查询编辑器启动 DAC
解密存储过程(sp_DecryptObject),

只能一次对一个存储过程、函数、视图或触发器,进行解密:
代码如下:

use test 
go 
exec sp_DecryptObject MyTrigger 
go


当定义内容长度超过4000,我们可以指定@MaxLength的值,如:
代码如下:

exec sp_DecryptObject fn_My,20000 
go 
这里(fn_My)是一个函数,定义内容超过了8000: 
... ...


小结:
虽然,上面的脚本,我已经在SQL Server 2008 R2 和SQL Server 2012测试过,

但无法避免一些未知错误 。

如果你自己在测试上面的脚本,请不要在生产环境上。

如果你在应用过程,碰到有什么问题或有什么意见和建议可以发email联系我或跟帖,在此非常感谢!


  • 上一条:
    sql2008安装后服务器改名造成名称不一致解决方法
    下一条:
    SQL Server 2008登录错误:无法连接到(local)解决方法
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 分库分表的目的、优缺点及具体实现方式介绍(0个评论)
    • DevDB - 在 VS 代码中直接访问数据库(0个评论)
    • 在ubuntu系统中实现mysql数据存储目录迁移流程步骤(0个评论)
    • 在mysql中使用存储过程批量新增测试数据流程步骤(0个评论)
    • php+mysql数据库批量根据条件快速更新、连表更新sql实现(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-06
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-05
    • 2018-10
    • 2018-11
    • 2020-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2021-02
    • 2021-04
    • 2021-07
    • 2021-08
    • 2021-11
    • 2021-12
    • 2022-02
    • 2022-03
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-07
    • 2023-08
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-03
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客