iOS图片隐藏信息/暗水印
LSB
LSB 是“Least Significant Bit”的缩写,意为“最低有效位”。在二进制中,LSB 是一个二进制数中的最右边(最低位)的位,通常在从右向左数第 0 位。LSB 所在的位置对于数值的大小影响最小,因为它代表的是最小的权值。
举例来说,对于二进制数 1011:
- 最右边的位是 LSB,即 1,表示 1。
- 最左边的位是最高有效位(Most Significant Bit,MSB),即 1,表示 8。
效果展示
原理
将图片转化成bitMap,对每一个像素点的数据的最低位进行修改。改成我们需要的数据。
比如这是utf8编码下的 l
字符
我们使用8个像素点,对每个像素点的最低位进行修改,变成 l
字符对应的数据。这样每8个像素点就可以存一个byte。对于每个像素点,只改动了红色分量最低位的,基本上不会影响图片的显示效果。
encodeMsg
// 将信息存进image
func encodeMsgIntoImg(msg: String, image: UIImage) -> UIImage {
guard let cgImg = image.cgImage else { return image }
let width = cgImg.width
let height = cgImg.height
guard let colorSpace = cgImg.colorSpace else { return image }
// 创建的上下文是 每个分量8Bit,每个像素四个字节,bitMapInfo是 RGBX
var imagePixel = [UInt32](repeating: 0, count: width * height)
// data: 指向 imagePixel 数组的指针
guard let context = CGContext(data: &imagePixel,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 4 * width,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else {
return image
}
// 将图片绘制到上下文中
context.draw(cgImg, in: CGRect(x: 0, y: 0, width: width, height: height))
// 存数据需要有个key用来判断该图片是否存了数据,我们在取的时候,优先把前面的key的字节数据读出来
// 如果数据解析跟我们的msgKey相同,说明该图片有我们的数据
let msgKey = "lcm"
// 用来记录我们存进去的信息有多少字节
// 默认使用2字节=16bytes来记录内容长度,即最大65535
// 但真正能记录的内容长度需要由载体图片的大小解决。
// 以 iPhone4 最小的分辨率 640 x 960,其能记录的隐体信息数值最大为: 614400,已经远大于 65535
// 所以根据需求 可以使用 更多的字节来记录内容长度
let contentLenByteCount = 2 // 两个字节 记录 内容长度
// 把 key 和 content分别转换成二进制数据
let keyData = msgKey.data(using: .utf8)!
let keyDataCount = keyData.count
let contentData = msg.data(using: .utf8)!
let contentDataCount = contentData.count
// 我们把 key和content长度字节称为header 内容称为content
// markCount 是header 和content的总的字节长度
let markCount = keyDataCount + contentLenByteCount + contentDataCount
// 循环 heander + content,每次存一个字节
for i in 0..<markCount {
// 声明一个 UInt8,表示当前存的字节
var cotInt8: UInt8 = 0
if i < keyDataCount { // 下标小于keyDataCount 说明存key
cotInt8 = keyData[i]
} else if i < keyDataCount + contentLenByteCount { // 下标小于 header 的长度,存内容长度的那两个字节
let lenOffset = i - keyDataCount
let lenData: UInt16 = UInt16(contentDataCount) // 内容长度的数据
// 根据下标确定是第一个字节还是第二个字节
cotInt8 = UInt8((lenData >> (8*(contentLenByteCount - lenOffset - 1))) & 0xff)
} else if i < markCount {
// 内容字节
let conOffset = i - keyDataCount - contentLenByteCount
cotInt8 = contentData[conOffset]
}
let beginPixelIndex = i*8
let endPixelIndex = i*8 + 7
for j in beginPixelIndex...endPixelIndex { // 对应像素点的下标,遍历存每一个bit
let bitIndex = j - beginPixelIndex
let targetBit = cotInt8 & (0x01 << (7 - bitIndex)) == 0 ? 0 : 1
imagePixel[j] = ((imagePixel[j] >> 1) << 1) | UInt32(targetBit)
}
}
guard let finalImg = context.makeImage() else {
return image
}
return UIImage(cgImage: finalImg)
}
decodeMsg
func decodeMsg(image: UIImage) -> String? {
guard let cgImg = image.cgImage else { return nil }
let width = cgImg.width
let height = cgImg.height
guard let colorSpace = cgImg.colorSpace else { return nil }
var imagePixel = [UInt32](repeating: 0, count: width * height)
guard let context = CGContext(data: &imagePixel,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: 4 * width,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.noneSkipLast.rawValue) else {
return nil
}
context.draw(cgImg, in: CGRect(x: 0, y: 0, width: width, height: height))
let msgKey = "lcm"
let contentLenByteCount = 2 // 两个字节 记录 内容长度
let keyData = msgKey.data(using: .utf8)!
let keyDataCount = keyData.count
// 计算 header的字节长度
let headCount = keyDataCount + contentLenByteCount
if imagePixel.count < headCount { return nil }
var headData: [UInt8] = []
// 根据header的字节长度 把每个像素点的最后一位取出来
for i in 0..<headCount {
var item: UInt8 = 0
for j in 0..<8 {
let index = i*8 + j
let bit = imagePixel[index] & 0x01
item = (item << 1) | UInt8(bit)
}
headData.append(item)
}
// 遍历key的每个字节,判断是否相等,不想等说明该图片没有存信息 return
for i in 0..<keyDataCount {
if headData[i] != keyData[i] { return nil }
}
// 取出内容长度
let conCount = (Int(headData[keyDataCount]) << 8) + Int(headData[keyDataCount + 1])
if conCount == 0 { return nil }
// 根据内容长度 取出内容
var resultData: [UInt8] = []
for i in headCount..<(headCount + conCount) {
var item: UInt8 = 0
for j in 0..<8 {
let index = i*8 + j
let bit = imagePixel[index] & 0x01
item = (item << 1) | UInt8(bit)
}
resultData.append(item)
}
// 将取出来的内容decode
if let decodedString = String(bytes: resultData, encoding: .utf8) {
return decodedString
} else {
return nil
}
}