Last active
November 30, 2017 03:09
-
-
Save pudquick/785a464ab1b0fffe5fa7 to your computer and use it in GitHub Desktop.
For my blog post at http://michaellynn.github.io/2017/07/26/exploring-os-x-preview-signatures/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Foundation import * | |
def readPlistFromData(data_obj): | |
# Reads binary and XML plists from a NSData object / python string / raw bytes | |
every_byte = ''.join([x for x in data_obj]) | |
nsdata_obj = NSData.dataWithBytes_length_(every_byte, len(every_byte)) | |
data, p_format, error = NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(nsdata_obj, \ | |
NSPropertyListMutableContainersAndLeaves, None, None) | |
return data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Foundation import * | |
import os.path | |
signature_array = CFPreferencesCopyValue('items-1', \ | |
os.path.expanduser('~/Library/Containers/com.apple.Preview/Data/Library/Preferences/com.apple.Preview.signatures.plist'), \ | |
kCFPreferencesCurrentUser, kCFPreferencesAnyHost) | |
first_sig = signature_array[0] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sig_plist = readPlistFromData(first_sig) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Foundation import * | |
sig_decoded = NSKeyedUnarchiver.unarchiveObjectWithData_(first_sig) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@interface PVSignature : NSObject <NSCoding> | |
{ | |
NSData *_undecryptedData; | |
BOOL _cannotDecrypt; | |
PVSignaturePayload *_payload; | |
NSImage *_largeThumbnail; | |
NSImage *_largeWhiteThumbnail; | |
NSImage *_smallThumbnail; | |
BOOL _thumbnailsGenerated; | |
int _uid; | |
BOOL _shouldPersist; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Foundation import * | |
class PVSignature(NSObject): | |
_undecryptedData = None | |
_uid = None | |
_cannotDecrypt = None | |
_shouldPersist = None | |
_largeThumbnail = None | |
_smallThumbnail = None | |
_payload = None | |
def init(self): | |
self = super(PVSignature, self).init() | |
if self == None: return None | |
self._undecryptedData = None | |
self._uid = None | |
self._cannotDecrypt = None | |
self._shouldPersist = None | |
self._largeThumbnail = None | |
self._smallThumbnail = None | |
self._payload = None | |
return self | |
def initWithCoder_(self, coder): | |
# use decodeObjectForKey when the key value could be something other than a primitive data type (looks like NSData here...) | |
self._undecryptedData = coder.decodeObjectForKey_(u"data") | |
# use decodeIntForKey because it's pretty obvious from sig_plist['$objects'][1] | |
self._uid = coder.decodeInt32ForKey_(u"uid") | |
self._shouldPersist = 1 | |
# We won't set the other values to 0, because in this context that's actually a null pointer for them | |
return self | |
# Define our class for itself | |
NSKeyedUnarchiver.setClass_forClassName_(PVSignature, "PVSignature") | |
# Attempt to decode again! | |
sig_decoded = NSKeyedUnarchiver.unarchiveObjectWithData_(first_sig) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import binascii | |
hex_key = 'E29D5B36A433C4E592B7A3991EF8691C' | |
binary_key = binascii.unhexlify(hex_key) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Crypto.Cipher import AES | |
iv = '\x00'*16 # 16 byte character array of zeroes | |
cipher = AES.new(binary_key, AES.MODE_CBC, IV=iv) | |
decrypted_data = cipher.decrypt(str(sig_decoded._undecryptedData)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class PKCS7Encoder(): | |
class InvalidBlockSizeError(Exception): | |
"""Raised for invalid block sizes""" | |
pass | |
def __init__(self, block_size=16): | |
if block_size < 2 or block_size > 255: | |
raise PKCS7Encoder.InvalidBlockSizeError('The block size must be ' \ | |
'between 2 and 255, inclusive') | |
self.block_size = block_size | |
def encode(self, text): | |
text_length = len(text) | |
amount_to_pad = self.block_size - (text_length % self.block_size) | |
if amount_to_pad == 0: | |
amount_to_pad = self.block_size | |
pad = chr(amount_to_pad) | |
return text + pad * amount_to_pad | |
def decode(self, text): | |
pad = ord(text[-1]) | |
return text[:-pad] | |
encoder = PKCS7Encoder() | |
unpadded_decrypted_data = encoder.decode(decrypted_data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
decrypted_plist = readPlistFromData(unpadded_decrypted_data[32:]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Foundation import * | |
nsdata_decrypted_plist = NSData.dataWithBytes_length_(unpadded_decrypted_data[32:], len(unpadded_decrypted_data[32:])) | |
real_sig_decoded = NSKeyedUnarchiver.unarchiveObjectWithData_(nsdata_decrypted_plist) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@interface PVSignaturePayload : NSObject <NSCoding> | |
{ | |
NSBezierPath *path; | |
double baselineHeight; | |
NSDate *creationDate; | |
NSDate *lastUsedDate; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Foundation import * | |
# This one isn't defined in Foundation | |
from AppKit import NSBezierPath | |
class PVSignaturePayload(NSObject): | |
path = None | |
baselineHeight = None | |
creationDate = None | |
lastUsedDate = None | |
def init(self): | |
self = super(PVSignaturePayload, self).init() | |
if self == None: return None | |
self.path = None | |
self.baselineHeight = None | |
self.creationDate = None | |
self.lastUsedDate = None | |
return self | |
def initWithCoder_(self, coder): | |
# use decodeObjectForKey when the key value could be something other than a primitive data type (looks like NSData here...) | |
self._undecryptedData = coder.decodeObjectForKey_(u"data") | |
# use decodeIntForKey because it's pretty obvious from sig_plist['$objects'][1] | |
self._uid = coder.decodeInt32ForKey_(u"uid") | |
self._shouldPersist = 1 | |
# We won't set the other values to 0, because in this context that's actually a null pointer for them | |
self.path = coder.decodeObjectForKey_(u"path") | |
self.baselineHeight = coder.decodeFloatForKey_(u"baselineHeight") | |
self.creationDate = coder.decodeObjectForKey_(u"creationDate") | |
self.creationDate = coder.decodeObjectForKey_(u"creationDate") | |
return self | |
# Define our class for itself | |
NSKeyedUnarchiver.setClass_forClassName_(PVSignaturePayload, "PVSignaturePayload") | |
# Define some of the other classes that come out | |
NSKeyedUnarchiver.setClass_forClassName_(NSDate, "NSDate") | |
NSKeyedUnarchiver.setClass_forClassName_(NSBezierPath, "NSBezierPath") | |
real_sig_decoded = NSKeyedUnarchiver.unarchiveObjectWithData_(nsdata_decrypted_plist) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from Foundation import * | |
from AppKit import * | |
import math | |
sig_path = real_sig_decoded.path | |
shift_path = NSAffineTransform.alloc().init() | |
origin = sig_path.bounds().origin | |
# We translate by the opposite X and Y values - with +0.1 to nudge it into the positive | |
shift_path.translateXBy_yBy_(origin.x * -1. + 0.1, origin.y * -1. + 0.1) | |
sig_path_shifted = sig_path.copy() | |
sig_path_shifted.transformUsingAffineTransform_(shift_path) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os.path | |
bounds = sig_path_shifted.bounds() | |
width = int(math.ceil(bounds.origin.x + bounds.size.width)) | |
height = int(math.ceil(bounds.origin.y + bounds.size.height)) | |
# Build a pixels bitmap to draw onto | |
signature_bitmap = NSBitmapImageRep.alloc().initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel_(None, width, height, 8, 4, True, False, NSDeviceRGBColorSpace, 0, 0) | |
# Save our current graphic information (we really don't care, but it's polite) | |
NSGraphicsContext.saveGraphicsState() | |
# Set the focus to our bitmap | |
NSGraphicsContext.setCurrentContext_(NSGraphicsContext.graphicsContextWithBitmapImageRep_(signature_bitmap)) | |
# Set the color to black | |
NSColor.blackColor().setFill() | |
# Fill our signature on the bitmap with black | |
sig_path_shifted.fill() | |
# Restore our previous graphics state | |
NSGraphicsContext.restoreGraphicsState() | |
# Build a data stream of a PNG from our bitmap | |
data = signature_bitmap.representationUsingType_properties_(NSPNGFileType, None) | |
# Save it to a file | |
result = data.writeToFile_atomically_(os.path.expanduser("~/Desktop/my_signature.png"), True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment