Channels ▼
RSS

Security

How to Secure and Authenticate Images Using Watermarks


Applying the Watermark

In the ImageSign category the apply() method (Listing 4) embeds the watermark into the target image. It uses the LSB algorithm described earlier in this article. The method begins by converting the image data into a block of raw pixels, tBuf (lines 18-22). Then it renders the watermark hash into an unsigned character stream, tBgn (lines 27-29).

Listing Four

// Apply the watermark to the target image
- (NSImage *)apply:(NSData *)aMrk
{
    CGFloat tRed, tGrn, tBlu, tAlp;
    NSUInteger tR, tG, tB;
    NSUInteger tX, tY, tXm, tYm;
    NSUInteger tLen, tIdx, tCnt, tLSR;
    
    NSImage *tMrk;
    NSColor *tOld, *tNew;
    NSData *tDat;
    NSBitmapImageRep *tBuf;
    
    unsigned long tByt, tLsb;
    unsigned char *tBgn, *tTmp, tChr;
    
    // convert the image data into a byte stream
    tDat = [self TIFFRepresentation];
    if (tDat != nil)
    {
        // prepare the pixel buffer
        tBuf = [NSBitmapImageRep imageRepWithData:tDat];
        [tBuf retain];
        if (tBuf != nil)
        {
            // prepare the watermark buffer
            tLen = [aMrk length];
            tBgn = (unsigned char *)malloc(tLen);
            [aMrk getBytes:tBgn];
                        
            // start parsing the buffer
            tXm = [tBuf pixelsWide];
            tYm = [tBuf pixelsHigh];
            for (tY = 0; tY < tYm; tY++) 
            {
                // read the first signature byte
                tTmp = tBgn;
                tChr = *tTmp++;
                tByt = (unsigned long)tChr;
                tIdx = 1;
                tCnt = 8;
                
                for (tX = 0; tX < tXm; tX++) 
                {
                    // read the original colour
                    tOld = [tBuf colorAtX:tX y:tY];
                    
                    // extract the colour bytes
                    [tOld getRed:&tRed green:&tGrn 
                            blue:&tBlu alpha:&tAlp];
                    
                    // convert the normalised colours
                    tR = (long)(tRed * 255);
                    tG = (long)(tGrn * 255);                    
                    tB = (long)(tBlu * 255);
                    
                    // mask out the least significant bits
                    tR &= 0xfc;
                    tG &= 0xfc;
                    tB &= 0xfc;
                    
                    // insert the watermark bits
                    // -- colour:byte:red
                    tLSR = tCnt - 2;
                    tLsb = (tByt >> tLSR);
                    tLsb &= 0x03;
                    tR |= tLsb;
                    
                    // -- colour:byte:green
                    tLSR = tCnt - 4;
                    tLsb = (tByt >> tLSR);
                    tLsb &= 0x03;
                    tG |= tLsb;
                    
                    // -- colour:byte:blue
                    tLSR = tCnt - 6;
                    tLsb = (tByt >> tLSR);
                    tLsb &= 0x03;
                    tB |= tLsb;
                    
                    // check the bit counter
                    tCnt -= 6;
                    if (tCnt < 6)
                    {
                        // check the byte index
                        if (tIdx == tLen)
                        {
                            // reset the watermark buffer
                            tTmp = tBgn;
                            tIdx = 1;
                        }
                        else
                            tIdx++;
                        
                        // update the bit buffer
                        switch (tCnt)
                        {
                            case 2:
                                tByt &= 0x03;
                                tByt <<= 8;
                                break;
                            case 4:
                                tByt &= 0x0f;
                                tByt <<= 8;
                                break;
                            default:
                                tByt = 0;
                        } // switch (tCnt)
                        tChr = *tTmp++;
                        tByt |= (unsigned long)tChr;
                        tCnt += 8;
                    }
                    else if (tCnt == 6)
                        tByt &= 0x3f;
                    
                    // prepare the normalised colours
                    tRed = tR / 255.0;
                    tGrn = tG / 255.0;
                    tBlu = tB / 255.0;
                    
                    // prepare the modified colour
                    tNew = [NSColor colorWithDeviceRed:tRed 
                                                 green:tGrn 
                                                  blue:tBlu 
                                                 alpha:tAlp];
                    
                    // apply the modified colour
                    [tBuf setColor:tNew atX:tX y:tY];
                } // for (tX = 0; tX < tXm; tX++) 
                
            } // for (tY = 0; tY < tYm; tY++) 
            
            // prepare the marked image
            tDat = [tBuf TIFFRepresentation];
            tMrk = [[NSImage alloc] initWithData:tDat];
            
            // dispose the buffers
            [tBuf release];
            free(tBgn);
        } // if (tBuf != nil)
    } // if (tDat != nil)
    
    // return the marked image
    return ([tMrk autorelease]);
}

Next, the apply() method parses the raw pixel block, moving from top to bottom, left to right. At the start of each pixel row, the method reads the first byte from the watermark hash, tChr (lines 37-41). Then it reads a pixel from the block and divides that pixel into bytes of red, tR; green, tG; and blue, tB (lines 46-55). Note the method assumes a 24-bit RGB as the color model.

The apply() method masks out bits 0 and 1 from each color byte (lines 58-60). It reads two bits from the hash byte tChr and inserts them into the red byte tR. Then it repeats the same steps for green and blue (lines 64-79). Once all three color bytes were modified, apply()decrements the bit counter tCnt by six (line 82). If the counter falls below eight, the method reads the next watermark byte. It then shifts tChr to the left by eight and appends the new byte to the result (lines 93-108). But if all the watermark bytes were used, apply() resets the tTmp buffer back to its starting address (lines 89-90).

The apply()method then normalizes the modified color bytes and writes them to the pixel buffer, using the same position as the unchanged pixel (lines 117-128). After it finishes a row of pixels, the method resets the watermark buffer and the two counters (lines 37-41), and again reads the first watermark byte. Once it has modified the pixel buffer, apply() uses it to create a new NSImage object tMrk. It disposes the pixel and watermark buffers, and returns the image object with its autorelease flag set (lines 134-144).

Now the apply() expects its host NSImage to have a valid image data. Otherwise, apply()will fail quietly, without any errors.

Measuring the Watermark

The next method in the ImageSign category is the coverage() method (Listing 5). This one examines the image data and counts the number of intact watermarks it finds.

Listing Five

// Measure the watermark coverage on the target image
- (NSNumber *)coverage:(NSData *)aMrk;
{
    CGFloat tRed, tGrn, tBlu, tAlp;
    NSUInteger tR, tG, tB;
    NSUInteger tX, tY, tXm, tYm;
    NSUInteger tFnd, tCnt;
    
    NSBitmapImageRep *tBuf;
    NSColor *tPix;
    NSData *tDat;
    NSMutableData *tMrk;
    
    unsigned long tByt;
    unsigned char tChr;
    double tRef, tPer;
    
    // convert the image data into a byte stream
    tDat = [self TIFFRepresentation];
    tPer = 0.0;
    if (tDat != nil)
    {
        // prepare the pixel buffer
        tBuf = [NSBitmapImageRep imageRepWithData:tDat];
        
        // prepare the watermark buffer
        tMrk = [NSMutableData dataWithCapacity:0];
        
        if ((tBuf != nil) && (tMrk != nil))
        {
            // initialise the following locals
            tXm = [tBuf pixelsWide];
            tYm = [tBuf pixelsHigh];
            tFnd = 0;
            
            // calculate the expected number of watermark matches
            tRef = tXm * tYm * 3.0 / 64.0;
            
            // start parsing the pixel buffer
            for (tY = 0; tY <= tYm; tY++) 
            {
                // prepare to parse a pixel row
                tByt = 0x00;
                tCnt = 0;
                [tMrk initWithLength:0];
                
                // parse the row
                for (tX = 0; tX <= tXm; tX++) 
                {
                    // read the pixel colour
                    tPix = [tBuf colorAtX:tX y:tY];
                    
                    // extract the colour bytes
                    [tPix getRed:&tRed green:&tGrn 
                            blue:&tBlu alpha:&tAlp];
                    
                    // convert the normalised colours
                    tR = (long)(tRed * 255);
                    tG = (long)(tGrn * 255);                    
                    tB = (long)(tBlu * 255);
                    
                    // reconstruct the watermark byte
                    tR &= 0x03;
                    switch (tCnt)
                    {
                        case 0:
                            tR <<= 6;
                            tByt |= tR;
                            tCnt += 2;
                            break;
                        case 2:
                            tR <<= 4;
                            tByt |= tR;
                            tCnt += 2;
                            break;
                        case 4:
                            tR <<= 2;
                            tByt |= tR;
                            tCnt += 2;
                            break;
                        case 6:
                            tByt |= tR;
                            tCnt = 0;
                            
                            // update the watermark buffer
                            tChr = (char)tByt;
                            [tMrk appendBytes:&tChr length:1];
                            if ([tMrk length] == [aMrk length])
                            {
                                if ([tMrk isEqualToData:aMrk])
                                    tFnd++;
                                [tMrk initWithLength:0];
                            }
                            // prepare to receive the next watermark byte
                            tByt = 0x00;
                    }
                    
                    tG &= 0x03;
                    switch (tCnt)
                    {
                        case 0:
                            tG <<= 6;
                            tByt |= tG;
                            tCnt += 2;
                            break;
                        case 2:
                            tG <<= 4;
                            tByt |= tG;
                            tCnt += 2;
                            break;
                        case 4:
                            tG <<= 2;
                            tByt |= tG;
                            tCnt += 2;
                            break;
                        case 6:
                            tByt |= tG;
                            tCnt = 0;
                            
                            // update the watermark buffer
                            tChr = (char)tByt;
                            [tMrk appendBytes:&tChr length:1];
                            if ([tMrk length] == [aMrk length])
                            {
                                if ([tMrk isEqualToData:aMrk])
                                    tFnd++;
                                [tMrk initWithLength:0];
                            }
                            // prepare to receive the next watermark byte
                            tByt = 0x00;
                    }
                    
                    tB &= 0x03;
                    switch (tCnt)
                    {
                        case 0:
                            tB <<= 6;
                            tByt |= tB;
                            tCnt += 2;
                            break;
                        case 2:
                            tB <<= 4;
                            tByt |= tB;
                            tCnt += 2;
                            break;
                        case 4:
                            tB <<= 2;
                            tByt |= tB;
                            tCnt += 2;
                            break;
                        case 6:
                            tByt |= tB;
                            tCnt = 0;
                            
                            // update the watermark buffer
                            tChr = (char)tByt;
                            [tMrk appendBytes:&tChr length:1];
                            if ([tMrk length] == [aMrk length])
                            {
                                if ([tMrk isEqualToData:aMrk])
                                    tFnd++;
                                [tMrk initWithLength:0];
                            }
                            // prepare to receive the next watermark byte
                            tByt = 0x00;
                    }
                    
                } // for (tX = 0; tX <= tXm; tX++) 
            } // for (tY = 0; tY <= tYm; tY++)
            
            // calculate the percent coverage
            tPer = tFnd * 100.0 / tRef;
            
        } // if ((tBuf != nil) && (tMrk != nil))
    } // if (tDat != nil)
    
     // return the metric result
    return ([NSNumber numberWithDouble:tPer]);
}

Again, the method starts by rendering the image data into a block of raw pixels tBuf (lines 18-22). Then it creates an empty NSData object tMrk, with which to hold the watermark bytes (line 27). It also computes the expected number of watermarks, based on the image size and color depth (line 37). Now the coverage() method parses the pixel buffer from top to bottom, left to right. At the start of each row, it resets its byte buffer tByt, its bit counter, tCnt and the watermark buffer tMrk (lines 43-45). Next, it reads a pixel from the buffer and divides the pixel into bytes of red, green and blue. From each byte, it extracts bits 0 and 1 and appends them to tByt (lines 66-83 for red byte).

Once tByt holds eight bits, coverage() appends the byte to the buffer tMrk (lines 121-122 for red byte). It resets the bit buffer tByt and the bit counter tCnt. Then it extracts the next eight bits. Once tMrk holds 16 bytes, the size of an MD5 hash value, coverage() compares it against the expected watermark aMrk. If both watermarks match, the method increments the tFnd counter (lines 90-92). And it clears tMrk, making it ready to hold the next watermark.

Finally, once coverage() has parsed all of the image pixels, it calculates how much of the image was covered by the watermark (line 172). It returns the result, a percentage, as an NSNumber object (line 178).


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video