二维码加密/解密(防篡改) 工具实现
概述
详细
前言
在任何领域,图像资源是很容易被获取甚至恶意替换的,此工具提供一种解决思路,把图像转换为0/1压缩矩阵字符串,然后运行程序时候,解析字符串展示出图像来。图像和代码融为一体,这种情况下,除非破解源代码,否则束手无策。 这种方式,对于二维码图像,尤其适用,转换成字符串也比较小。下面具体讲讲实现原理和过程。
效果展示
此为重要的收款码,别人将无法篡改,最终,只会在程序 中 存储 密钥信息,运行程序进行展示,没有图像资源。
工具实现过程
1.准备c# WFM窗体应用程序(作为入口)
如下图, 创建窗体程序以及相关的控件(输入框、按钮等)
2. 实现拖拽读取图片功能
相关代码
private void Form1_DragEnter(object sender, DragEventArgs e) { string fileInfo = ((System.Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString(); if (fileInfo.EndsWith(".png") && !textBox1.Text.Equals(fileInfo)) { textBox1.Text = fileInfo; Common.DealImgCodeToZeroStepOne(textBox1.Text); Common.DealTrimStepTwo(); string res = Common.DealCodeToStr(); richTextBox1.Text = res; } }
3. 图像加密,转换字符串
图像转换压缩字符串过程 是,第一步,把图像 所有的像素点,规划为0/1两种,黑白两种,也就是 对 图像的位图 处理,处理完之后, 第二步 ,二维码四周 的空白像素 是 不要的,是冗余信息,需要删除,进行处理之后,进行第三步,统计压缩数据,因为遍历是每一行一行进行的,相邻的几行 是一样的,需要 压缩 处理 即,使用 5> 表示,有5行 一样的像素信息,大大降低转换后存储大小。至此,转换完成, 需要特别注意的是, 一般截图二维码,像素点RGB不是完全的0,0,0,可能是有值的,黑白直接还有过度颜色,不可以单纯以二维码 单位块,一块一块的去计算转换,是有问题的。
下面是相关代码块,有点复杂,请仔细看。转换完成之后,生成比较少量的字符串,此为整理压缩后的数据矩阵。将在 展示 图像中去解析使用此字符串。
//第一步: 二维码处理 0/1 化 转换为 位图
public static void DealImgCodeToZeroStepOne(String imgDir) { Image curImg = Image.FromFile(imgDir); int imgWidth = curImg.Width; int imgHeight = curImg.Height; Bitmap curBitImg = new Bitmap(curImg); curDealImg = new Bitmap(imgWidth, imgHeight); for (int y = 0; y < imgHeight; y++) { for (int x = 0; x < imgWidth ; x++) { Color pixel = curBitImg.GetPixel(x, y); if (pixel.R <= 2&& pixel.G <= 2 && pixel.B <= 2) { curDealImg.SetPixel(x, y, Color.FromArgb(255, 0, 0, 0)); } else { curDealImg.SetPixel(x, y, Color.FromArgb(0, 0, 0, 0)); } } } }
//第二步: 删除边缘 无用 像素
public static void DealTrimStepTwo() { int imgHeight = curDealImg.Height; int imgWidth = curDealImg.Width; int startX = 0, endX = 0, startY = 0, endY = 0; int x, y; for (y = 0; y < imgHeight; y++) { if (startY != 0) break; for (x = 0; x < imgWidth; x++) { if (curDealImg.GetPixel(x, y).A != 0 && startY == 0) { startY = y; break; } } } for (y = imgHeight - 1; y >= 0; y--) { if (endY != 0) break; for (x = imgWidth - 1; x >= 0; x--) { if (curDealImg.GetPixel(x, y).A != 0 && endY == 0) { endY = y; break; } } } for (x = 0; x < imgWidth; x++) { if (startX != 0) break; for (y = 0; y < imgHeight; y++) { if (curDealImg.GetPixel(x, y).A != 0 && startX == 0) { startX = x; break; } } } for (x = imgWidth - 1; x >= 0; x--) { if (endX != 0) break; for (y = 0; y < imgHeight; y++) { if (curDealImg.GetPixel(x, y).A != 0 && endX == 0) { endX = x; break; } } } Color pixel; int afterWidth = endX - startX + 1; int afterHeight = endY - startY + 1; Bitmap resBmImg = new Bitmap(afterWidth, afterHeight); int x2, y2; for (y2 = 0; y2 < afterHeight; y2++) { for (x2 = 0; x2 < afterWidth; x2++) { pixel = curDealImg.GetPixel(startX + x2, startY + y2); resBmImg.SetPixel(x2, y2, Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B)); } } curDealImg = null; curDealImg = resBmImg; }
//第三步: 二维码处理 转换 压缩数据 字符串 加密完成
public static string DealCodeToStr() { int imgWidth = curDealImg.Width; int imgHeight = curDealImg.Height; string res = ""; for (int y = 0; y < imgHeight; y++) { int totalNum = 0; int oldType = -1; string curLineStr = ""; for (int x = 0; x < imgWidth; x++) { Color pixel = curDealImg.GetPixel(x, y); int curType = pixel.A == 255 ? 1 : 0; if (x == 0) { oldType = pixel.A == 255 ? 1 : 0; totalNum = 1; continue; } else if (curType == oldType) { totalNum = totalNum + 1; if (x == imgWidth - 1) { if (y == imgHeight - 1) { curLineStr = curLineStr + totalNum + "," + oldType; } else { curLineStr = curLineStr + totalNum + "," + oldType + "\n"; } } continue; } else if (curType != oldType) { curLineStr = curLineStr + totalNum + "," + oldType + " "; totalNum = 1; oldType = curType; if (x == imgWidth - 1){ if (y == imgHeight - 1) { curLineStr = curLineStr + totalNum + "," + oldType; } else { curLineStr = curLineStr + totalNum + "," + oldType + "\n"; } } } } if (y == 0) { res = "1>" + curLineStr; continue; } else { if (res.EndsWith(curLineStr)) { int endfindIndex = res.IndexOf(">" + curLineStr); string repeateNum = ""; if (endfindIndex <= 2) { repeateNum = res.Substring(0, endfindIndex); } else { int startIndex = endfindIndex - 5 ; int agofindIndex = res.IndexOf("\n", startIndex); repeateNum = res.Substring(agofindIndex + 1, endfindIndex - agofindIndex - 1); } int addRepeateNum = Int32.Parse(repeateNum) + 1; res = res.Replace(repeateNum + ">" + curLineStr, addRepeateNum + ">" + curLineStr); } else { res = res + "1>" + curLineStr; } if (!res.EndsWith("\n")) { int a = 41564; } } } resStr = res; return res; }
4. 图像展示,解密字符串
在上步中,已经完成了图像转换字符串过程了,程序中 ,只需要使用此字符串,即可展示二维码图像,展示原理, 比如生成 字符串 为 5>49,1 9,0 6,1 16,0 ......, 表示,有5行一样的像素, 49个为1 黑色。9个为0 白色 等等,其他行表示内容与此类似,解析只不过是,算出 总共的像素大小,长和宽度,声明 对应的 位图对象,在位图对象的每一个像素点 x,y 上,设置其应0/1 黑白 颜色 即可。需要创建个新窗体,进行设置。就是很多很多for循环套用 即可。具体代码如下
新建窗体 showCodeDialog,添加 如下 代码
//声明所需变量 private string resStr; private int width; private int height; private int gap = 50; //设置原始字符串 public void setCurStr(string str) { resStr = str; } // 计算出 二维码图像的长宽 public void dealSize() { string orgPayCodeStr; if (resStr.IndexOf("\r\n") >= 0) { orgPayCodeStr = resStr.Replace("\r\n", "*"); } else { orgPayCodeStr = resStr.Replace("\n", "*"); } string[] listOneLine = orgPayCodeStr.Split('*'); string firstStr = listOneLine[1]; int tempEndIndex = firstStr.IndexOf(">"); string[] lineInfolist = firstStr.Substring(tempEndIndex + 1).Split(' '); // 计算 宽度 width = -1; for (int k = 0; k < lineInfolist.Length; k++) { { string[] oneInfo = lineInfolist[k].Split(','); int repeatOnePixel = Int32.Parse(oneInfo[0]); width = width + repeatOnePixel; } } height = -1; for (int i = 0; i < listOneLine.Length; i++) { string lineStr = listOneLine[i]; int endIndex = lineStr.IndexOf(">"); string a = lineStr.Substring(0, endIndex); int repeatLines = Int32.Parse(a); for (int j = 0; j < repeatLines; j++) { height = height + 1; } } width = width + 1; height = height + 1; this.Size = new Size(width + gap * 2, height + gap *2); //设置窗体大小 } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); var g = e.Graphics; dealSize(); Bitmap resBitmap = new Bitmap(width, height); string orgPayCodeStr; if (resStr.IndexOf("\r\n") >= 0) { orgPayCodeStr = resStr.Replace("\r\n", "*"); } else { orgPayCodeStr = resStr.Replace("\n", "*"); } string[] listOneLine = orgPayCodeStr.Split('*'); int curLineIndex = -1; for (int i = 0; i < listOneLine.Length; i++) { string lineStr = listOneLine[i]; int endIndex = lineStr.IndexOf(">"); string a = lineStr.Substring(0, endIndex); int repeatLines = Int32.Parse(a); string[] lineInfolist = lineStr.Substring(endIndex + 1).Split(' '); for (int j = 0; j < repeatLines; j++) { curLineIndex = curLineIndex + 1; int x = -1; for (int k = 0; k < lineInfolist.Length; k++) { string[] oneInfo = lineInfolist[k].Split(','); int repeatOnePixel = Int32.Parse(oneInfo[0]); for (int z = 1; z <= repeatOnePixel; z++) { resBitmap.SetPixel(x + z, curLineIndex, Color.FromArgb(oneInfo[1] == "1" ? 255 : 0, 0, 0, 0)); if (z == repeatOnePixel) { x = x + z; } } } } } g.DrawImage(resBitmap, gap -10, 15); }
5. 总结
通过以上几步,已经实现了二维码的加密 /解密,其实本质就是 把二维码图像信息,转换为很简短的, 用 0/1>*等字符拼接起来的字符串,因为有自己的处理,字符串不会很大,程序中 用字符串代替了图片而已。 需要注意的一点是,一般使用的二维码 不可能是完完全全的 255/0 纯黑白的 图像,图像 中是有过度颜色的,不能按块来处理,这是比较麻烦的,需要使用上面的方法进行 处理。
二维码图像,是非常非常重要的,一般不要以图像资源进行处理显示,很容易被人替换,或者显示不正常, 采取此工具的方式, 除非破解代码,否则不可能把二维码 图像给破坏的。这是一种思路。大家可以多理解理解。
编写不易,请多支持,谢谢。
项目结构图